From 5fbe75636bbbfbc0215cf2bf636f4745646fad1a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 14 Oct 2022 01:48:08 +0300 Subject: [PATCH] Working on memory mapping --- Lynx.code-workspace | 5 +- UEFI/Makefile | 6 +- UEFI/src/Bitmap.cpp | 28 + UEFI/src/Lynx.c | 66 + UEFI/src/Memory/Memory.cpp | 146 ++ UEFI/src/Memory/PhysicalMemoryManager.cpp | 284 ++++ UEFI/src/Memory/VirtualMemoryManager.cpp | 119 ++ UEFI/src/Memory/liballoc_1_1.c | 787 ++++++++++ UEFI/src/Memory/liballoc_1_1.h | 90 ++ UEFI/src/Memory/memory.hpp | 433 ++++++ UEFI/src/Paging.c | 0 UEFI/src/VirtualMemory.c | 0 UEFI/src/bitmap.hpp | 33 + UEFI/src/cxxabi.cpp | 157 ++ UEFI/src/printf.c | 1606 +++++++++++++++++++++ UEFI/src/printf.h | 195 +++ UEFI/types.h | 17 + 17 files changed, 3970 insertions(+), 2 deletions(-) create mode 100644 UEFI/src/Bitmap.cpp create mode 100644 UEFI/src/Memory/Memory.cpp create mode 100644 UEFI/src/Memory/PhysicalMemoryManager.cpp create mode 100644 UEFI/src/Memory/VirtualMemoryManager.cpp create mode 100644 UEFI/src/Memory/liballoc_1_1.c create mode 100644 UEFI/src/Memory/liballoc_1_1.h create mode 100644 UEFI/src/Memory/memory.hpp delete mode 100644 UEFI/src/Paging.c delete mode 100644 UEFI/src/VirtualMemory.c create mode 100644 UEFI/src/bitmap.hpp create mode 100644 UEFI/src/cxxabi.cpp create mode 100644 UEFI/src/printf.c create mode 100644 UEFI/src/printf.h create mode 100644 UEFI/types.h diff --git a/Lynx.code-workspace b/Lynx.code-workspace index e5510cd..520742b 100644 --- a/Lynx.code-workspace +++ b/Lynx.code-workspace @@ -5,6 +5,9 @@ } ], "settings": { - "debug.allowBreakpointsEverywhere": true + "debug.allowBreakpointsEverywhere": true, + "files.associations": { + "efi.h": "c" + } } } diff --git a/UEFI/Makefile b/UEFI/Makefile index 8320c82..6bbea17 100644 --- a/UEFI/Makefile +++ b/UEFI/Makefile @@ -6,7 +6,8 @@ LD = ld OBJCOPY = objcopy C_SOURCES = $(shell find ./src -type f -name '*.c') -OBJ = $(C_SOURCES:.c=.o) +CPP_SOURCES = $(shell find ./src -type f -name '*.cpp') +OBJ = $(C_SOURCES:.c=.o) $(CPP_SOURCES:.cpp=.o) GNUEFI_RELEASE_VERSION=3.0.14 @@ -39,6 +40,9 @@ BOOTX64: $(OBJ) $(info Compiling $<) $(CC) -Ignu-efi/inc -Ignu-efi/inc/x86_64 -Ignu-efi/inc/protocol -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -c $< -o $@ +%.o: %.cpp + $(info Compiling $<) + $(CC) -Ignu-efi/inc -Ignu-efi/inc/x86_64 -Ignu-efi/inc/protocol -fpermissive -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -c $< -o $@ clean: rm -f $(NAME) $(OBJ) BOOTX64.EFI diff --git a/UEFI/src/Bitmap.cpp b/UEFI/src/Bitmap.cpp new file mode 100644 index 0000000..6668d68 --- /dev/null +++ b/UEFI/src/Bitmap.cpp @@ -0,0 +1,28 @@ +#include "bitmap.hpp" + +bool Bitmap::operator[](uint64_t index) { return Get(index); } + +bool Bitmap::Get(uint64_t index) +{ + if (index > Size * 8) + return false; + uint64_t byteIndex = index / 8; + uint8_t bitIndex = index % 8; + uint8_t bitIndexer = 0b10000000 >> bitIndex; + if ((Buffer[byteIndex] & bitIndexer) > 0) + return true; + return false; +} + +bool Bitmap::Set(uint64_t index, bool value) +{ + if (index > Size * 8) + return false; + uint64_t byteIndex = index / 8; + uint8_t bitIndex = index % 8; + uint8_t bitIndexer = 0b10000000 >> bitIndex; + Buffer[byteIndex] &= ~bitIndexer; + if (value) + Buffer[byteIndex] |= bitIndexer; + return true; +} diff --git a/UEFI/src/Lynx.c b/UEFI/src/Lynx.c index 91a7e30..e7391a2 100644 --- a/UEFI/src/Lynx.c +++ b/UEFI/src/Lynx.c @@ -1,13 +1,79 @@ #include #include +#include "Memory/memory.hpp" #include "FileLoader.h" +#include "printf.h" + +void port_byte_out(short unsigned int port, unsigned char value) +{ + __asm__ volatile("outb %0, %1" + : + : "a"(value), "Nd"(port)); +} + +unsigned char port_byte_in(short unsigned int port) +{ + unsigned char ReturnValue; + __asm__ volatile("inb %1, %0" + : "=a"(ReturnValue) + : "Nd"(port)); + return ReturnValue; +} + +int strlen(const char s[]) +{ + int i = 0; + while (s[i] != '\0') + ++i; + return i; +} + +int init_serial() +{ + // TODO: fix crash on virtualbox (or virtualbox is faulty???????????) + port_byte_out(0x3F8 + 1, 0x00); + port_byte_out(0x3F8 + 3, 0x80); + port_byte_out(0x3F8 + 0, 0x03); + port_byte_out(0x3F8 + 1, 0x00); + port_byte_out(0x3F8 + 3, 0x03); + port_byte_out(0x3F8 + 2, 0xC7); + port_byte_out(0x3F8 + 4, 0x0B); + port_byte_out(0x3F8 + 4, 0x1E); + port_byte_out(0x3F8 + 0, 0xAE); + if (port_byte_in(0x3F8 + 0) != 0xAE) + { + return -1; // serial port is faulty + } + port_byte_out(0x3F8 + 4, 0x0F); + return 0; +} + +void printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + printf_(format, args); + va_end(args); +} + +extern void putchar(char c) +{ + while ((port_byte_in(0x3F8 + 5) & 0x20) == 0) + ; + port_byte_out(0x3F8, c); +} + EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL); Print(L"Lynx Bootloader © EnderIce2 2022\n"); + Print(L"UEFI not implemented\n"); + while (1) + asm("hlt"); + InitializeMemoryManagement(ImageHandle, SystemTable); EFI_FILE *Kernel = LoadFile(NULL, L"kernel.fsys", ImageHandle, SystemTable); if (Kernel == NULL) diff --git a/UEFI/src/Memory/Memory.cpp b/UEFI/src/Memory/Memory.cpp new file mode 100644 index 0000000..3dc6453 --- /dev/null +++ b/UEFI/src/Memory/Memory.cpp @@ -0,0 +1,146 @@ +#include "memory.hpp" + +#include "liballoc_1_1.h" + +extern "C" void printf(const char *format, ...); + +extern uint64_t ImageBase, _text, _etext, _data, _edata, _data_size; + +using namespace Memory; + +Physical KernelAllocator; +PageTable *KernelPageTable = nullptr; + +static void *memset(void *s, int c, size_t n) +{ + unsigned int i; + for (i = 0; i < n; i++) + ((char *)s)[i] = c; + + return s; +} + +extern "C" void InitializeMemoryManagement(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) +{ + printf("Initializing Physical Memory Manager\n"); + KernelAllocator = Physical(); + KernelAllocator.Init(ImageHandle, SystemTable); + printf("Memory Info: %dMB / %dMB (%dMB reserved)", + (KernelAllocator.GetUsedMemory() / 1024 / 1024), + (KernelAllocator.GetTotalMemory() / 1024 / 1024), + (KernelAllocator.GetReservedMemory() / 1024 / 1024)); + + KernelPageTable = (PageTable *)KernelAllocator.RequestPage(); + memset(KernelPageTable, 0, PAGE_SIZE); + Virtual kva = Virtual(KernelPageTable); + printf("Mapping...\n"); + + uint64_t BootloaderStart = (uint64_t)&ImageBase; + uint64_t BootloaderTextEnd = (uint64_t)&_text; + uint64_t BootloaderDataEnd = (uint64_t)&_data; + uint64_t BootloaderEnd = (uint64_t)&ImageBase + (uint64_t)&_etext + (uint64_t)&_edata; + + uint64_t VirtualOffsetNormalVMA = NORMAL_VMA_OFFSET; + uint64_t BaseKernelMapAddress = (uint64_t)0; // TODO: Info->Kernel.PhysicalBase; + + EFI_MEMORY_DESCRIPTOR *memDesc = nullptr; + UINTN MapSize, MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + { + SystemTable->BootServices->GetMemoryMap(&MapSize, memDesc, &MapKey, &DescriptorSize, &DescriptorVersion); + SystemTable->BootServices->AllocatePool(EfiLoaderData, MapSize, (void **)&memDesc); + SystemTable->BootServices->GetMemoryMap(&MapSize, memDesc, &MapKey, &DescriptorSize, &DescriptorVersion); + } + + for (uint64_t t = 0; t < MapSize / DescriptorSize; t += PAGE_SIZE) + { + kva.Map((void *)t, (void *)t, PTFlag::RW); + kva.Map((void *)VirtualOffsetNormalVMA, (void *)t, PTFlag::RW); + VirtualOffsetNormalVMA += PAGE_SIZE; + } + + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; + UINTN SizeOfInfo, numModes = 0; //, MaximumSupportedMode = 0; + EFI_STATUS status; + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; + EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + status = uefi_call_wrapper(BS->LocateProtocol, 3, &gopGuid, NULL, (void **)&gop); + if (EFI_ERROR(status)) + { + printf("Unable to locate the Graphics Output Protocol.\n"); + } + status = uefi_call_wrapper(gop->QueryMode, 4, gop, gop->Mode == NULL ? 0 : gop->Mode->Mode, &SizeOfInfo, &info); + if (status == EFI_NOT_STARTED) + { + printf("The EFI not started!\n"); + status = uefi_call_wrapper(gop->SetMode, 2, gop, 0); + } + + /* Mapping Framebuffer address */ + int itrfb = 0; + while (1) + { + for (uint64_t fb_base = (uint64_t)gop->Mode->FrameBufferBase; + fb_base < ((uint64_t)gop->Mode->FrameBufferBase + ((gop->Mode->Info->PixelsPerScanLine) + PAGE_SIZE)); + fb_base += PAGE_SIZE) + kva.Map((void *)fb_base, (void *)fb_base, PTFlag::RW | PTFlag::US); + itrfb++; + } + + /* Kernel mapping */ + for (uint64_t k = BootloaderStart; k < BootloaderTextEnd; k += PAGE_SIZE) + { + kva.Map((void *)k, (void *)BaseKernelMapAddress, PTFlag::RW); + KernelAllocator.LockPage((void *)BaseKernelMapAddress); + BaseKernelMapAddress += PAGE_SIZE; + } + + for (uint64_t k = BootloaderTextEnd; k < BootloaderDataEnd; k += PAGE_SIZE) + { + kva.Map((void *)k, (void *)BaseKernelMapAddress, PTFlag::RW); + KernelAllocator.LockPage((void *)BaseKernelMapAddress); + BaseKernelMapAddress += PAGE_SIZE; + } + + for (uint64_t k = BootloaderDataEnd; k < BootloaderEnd; k += PAGE_SIZE) + { + kva.Map((void *)k, (void *)BaseKernelMapAddress, PTFlag::RW); + KernelAllocator.LockPage((void *)BaseKernelMapAddress); + BaseKernelMapAddress += PAGE_SIZE; + } + + printf("\nStart: %#llx - Text End: %#llx - End: %#llx\nStart Physical: %#llx - End Physical: %#llx", + BootloaderStart, BootloaderTextEnd, BootloaderEnd, /* Info->Kernel.PhysicalBase */ 0, BaseKernelMapAddress - PAGE_SIZE); + + /* BootloaderStart BootloaderTextEnd KernelRoDataEnd BootloaderEnd + Kernel Start & Text Start ------ Text End ------ Kernel Rodata End ------ Kernel Data End & Kernel End + */ + printf("Applying new page table from address %p", KernelPageTable); + __asm__ volatile("mov %0, %%cr3" ::"r"(KernelPageTable)); +} + +extern "C" void *HeapMalloc(uint64_t Size) { return PREFIX(malloc)(Size); } +extern "C" void *HeapCalloc(uint64_t n, uint64_t Size) { return PREFIX(calloc)(n, Size); } +extern "C" void *HeapRealloc(void *Address, uint64_t Size) { return PREFIX(realloc)(Address, Size); } +extern "C" void HeapFree(void *Address) +{ + PREFIX(free) + (Address); +} + +void *operator new(uint64_t Size) { return HeapMalloc(Size); } +void *operator new[](uint64_t Size) { return HeapMalloc(Size); } +void operator delete(void *Pointer) { HeapFree(Pointer); } +void operator delete[](void *Pointer) { HeapFree(Pointer); } +void operator delete(void *Pointer, long unsigned int Size) { HeapFree(Pointer); } +void operator delete[](void *Pointer, long unsigned int Size) { HeapFree(Pointer); } + +EXTERNC int liballoc_lock() {} +EXTERNC int liballoc_unlock() {} +EXTERNC void *liballoc_alloc(size_t Pages) { return KernelAllocator.RequestPages(Pages); } +EXTERNC int liballoc_free(void *Address, size_t Pages) +{ + KernelAllocator.FreePages(Address, Pages); + return 0; +} diff --git a/UEFI/src/Memory/PhysicalMemoryManager.cpp b/UEFI/src/Memory/PhysicalMemoryManager.cpp new file mode 100644 index 0000000..80ba016 --- /dev/null +++ b/UEFI/src/Memory/PhysicalMemoryManager.cpp @@ -0,0 +1,284 @@ +#include "memory.hpp" + +extern "C" void printf(const char *format, ...); + +namespace Memory +{ + uint64_t Physical::GetTotalMemory() + { + return this->TotalMemory; + } + + uint64_t Physical::GetFreeMemory() + { + return this->FreeMemory; + } + + uint64_t Physical::GetReservedMemory() + { + return this->ReservedMemory; + } + + uint64_t Physical::GetUsedMemory() + { + return this->UsedMemory; + } + + bool Physical::SwapPage(void *Address) + { + printf("%p", Address); + return false; + } + + bool Physical::SwapPages(void *Address, uint64_t PageCount) + { + for (uint64_t i = 0; i < PageCount; i++) + if (!this->SwapPage((void *)((uint64_t)Address + (i * PAGE_SIZE)))) + return false; + return false; + } + + bool Physical::UnswapPage(void *Address) + { + printf("%p", Address); + return false; + } + + bool Physical::UnswapPages(void *Address, uint64_t PageCount) + { + for (uint64_t i = 0; i < PageCount; i++) + if (!this->UnswapPage((void *)((uint64_t)Address + (i * PAGE_SIZE)))) + return false; + return false; + } + + void *Physical::RequestPage() + { + for (; PageBitmapIndex < PageBitmap.Size * 8; PageBitmapIndex++) + { + if (PageBitmap[PageBitmapIndex] == true) + continue; + this->LockPage((void *)(PageBitmapIndex * PAGE_SIZE)); + return (void *)(PageBitmapIndex * PAGE_SIZE); + } + + if (this->SwapPage((void *)(PageBitmapIndex * PAGE_SIZE))) + { + this->LockPage((void *)(PageBitmapIndex * PAGE_SIZE)); + return (void *)(PageBitmapIndex * PAGE_SIZE); + } + + printf("Out of memory! (Free: %ldMB; Used: %ldMB; Reserved: %ldMB)", (FreeMemory / 1024 / 1024), (UsedMemory / 1024 / 1024), (ReservedMemory / 1024 / 1024)); + while (1) + __asm__("hlt"); + return nullptr; + } + + void *Physical::RequestPages(uint64_t Count) + { + for (; PageBitmapIndex < PageBitmap.Size * 8; PageBitmapIndex++) + { + if (PageBitmap[PageBitmapIndex] == true) + continue; + + for (uint64_t Index = PageBitmapIndex; Index < PageBitmap.Size * 8; Index++) + { + if (PageBitmap[Index] == true) + continue; + + for (uint64_t i = 0; i < Count; i++) + if (PageBitmap[Index + i] == true) + goto NextPage; + + this->LockPages((void *)(Index * PAGE_SIZE), Count); + return (void *)(Index * PAGE_SIZE); + + NextPage: + Index += Count; + continue; + } + } + + if (this->SwapPages((void *)(PageBitmapIndex * PAGE_SIZE), Count)) + { + this->LockPages((void *)(PageBitmapIndex * PAGE_SIZE), Count); + return (void *)(PageBitmapIndex * PAGE_SIZE); + } + + printf("Out of memory! (Free: %ldMB; Used: %ldMB; Reserved: %ldMB)", (FreeMemory / 1024 / 1024), (UsedMemory / 1024 / 1024), (ReservedMemory / 1024 / 1024)); + while (1) + __asm__("hlt"); + return nullptr; + } + + void Physical::FreePage(void *Address) + { + if (Address == nullptr) + { + printf("Null pointer passed to FreePage."); + return; + } + uint64_t Index = (uint64_t)Address / PAGE_SIZE; + if (PageBitmap[Index] == false) + return; + + if (PageBitmap.Set(Index, false)) + { + FreeMemory += PAGE_SIZE; + UsedMemory -= PAGE_SIZE; + if (PageBitmapIndex > Index) + PageBitmapIndex = Index; + } + } + + void Physical::FreePages(void *Address, uint64_t Count) + { + if (Address == nullptr || Count == 0) + { + printf("%s%s passed to FreePages.", Address == nullptr ? "Null pointer" : "", Count == 0 ? "Zero count" : ""); + return; + } + + for (uint64_t t = 0; t < Count; t++) + this->FreePage((void *)((uint64_t)Address + (t * PAGE_SIZE))); + } + + void Physical::LockPage(void *Address) + { + if (Address == nullptr) + printf("Trying to lock null address."); + + uint64_t Index = (uint64_t)Address / PAGE_SIZE; + if (PageBitmap[Index] == true) + return; + if (PageBitmap.Set(Index, true)) + { + FreeMemory -= PAGE_SIZE; + UsedMemory += PAGE_SIZE; + } + } + + void Physical::LockPages(void *Address, uint64_t PageCount) + { + if (Address == nullptr || PageCount == 0) + printf("Trying to lock %s%s.", Address ? "null address" : "", PageCount ? "0 pages" : ""); + + for (uint64_t i = 0; i < PageCount; i++) + this->LockPage((void *)((uint64_t)Address + (i * PAGE_SIZE))); + } + + void Physical::ReservePage(void *Address) + { + if (Address == nullptr) + printf("Trying to reserve null address."); + + uint64_t Index = (uint64_t)Address / PAGE_SIZE; + if (PageBitmap[Index] == true) + return; + + if (PageBitmap.Set(Index, true)) + { + FreeMemory -= PAGE_SIZE; + ReservedMemory += PAGE_SIZE; + } + } + + void Physical::ReservePages(void *Address, uint64_t PageCount) + { + if (Address == nullptr || PageCount == 0) + printf("Trying to reserve %s%s.", Address ? "null address" : "", PageCount ? "0 pages" : ""); + + for (uint64_t t = 0; t < PageCount; t++) + this->ReservePage((void *)((uint64_t)Address + (t * PAGE_SIZE))); + } + + void Physical::UnreservePage(void *Address) + { + if (Address == nullptr) + printf("Trying to unreserve null address."); + + uint64_t Index = (uint64_t)Address / PAGE_SIZE; + if (PageBitmap[Index] == false) + return; + + if (PageBitmap.Set(Index, false)) + { + FreeMemory += PAGE_SIZE; + ReservedMemory -= PAGE_SIZE; + if (PageBitmapIndex > Index) + PageBitmapIndex = Index; + } + } + + void Physical::UnreservePages(void *Address, uint64_t PageCount) + { + if (Address == nullptr || PageCount == 0) + printf("Trying to unreserve %s%s.", Address ? "null address" : "", PageCount ? "0 pages" : ""); + + for (uint64_t t = 0; t < PageCount; t++) + this->UnreservePage((void *)((uint64_t)Address + (t * PAGE_SIZE))); + } + + void Physical::Init(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) + { + printf("Initializing physical memory manager...\n"); + EFI_MEMORY_DESCRIPTOR *memDesc = nullptr; + UINTN MapSize, MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + { + SystemTable->BootServices->GetMemoryMap(&MapSize, memDesc, &MapKey, &DescriptorSize, &DescriptorVersion); + SystemTable->BootServices->AllocatePool(EfiLoaderData, MapSize, (void **)&memDesc); + SystemTable->BootServices->GetMemoryMap(&MapSize, memDesc, &MapKey, &DescriptorSize, &DescriptorVersion); + } + + uint64_t MemoryMapSize = MapSize / DescriptorSize; + static uint64_t MemorySizeBytes = 0; + + void *LargestFreeMemorySegment = nullptr; + uint64_t LargestFreeMemorySegmentSize = 0; + uint64_t MemorySize = MapSize; + TotalMemory = MemorySize; + FreeMemory = MemorySize; + + for (int i = 0; i < MemoryMapSize; i++) + { + EFI_MEMORY_DESCRIPTOR *Descriptor = (EFI_MEMORY_DESCRIPTOR *)((uint64_t)memDesc + (i * DescriptorSize)); + MemorySizeBytes += Descriptor->NumberOfPages * 4096; + switch (Descriptor->Type) + { + case EfiConventionalMemory: + if ((Descriptor->NumberOfPages * 4096) > LargestFreeMemorySegmentSize) + { + LargestFreeMemorySegment = (void *)Descriptor->PhysicalStart; + LargestFreeMemorySegmentSize = Descriptor->NumberOfPages * 4096; + printf("Largest free memory segment: %p (%dKB)", + (void *)Descriptor->PhysicalStart, + ((Descriptor->NumberOfPages * 4096) / 1024)); + } + break; + } + } + + uint64_t BitmapSize = ALIGN_UP((MemorySize / 0x1000) / 8, 0x1000); + printf("Initializing Bitmap (%p %dKB)", LargestFreeMemorySegment, (BitmapSize / 1024)); + + PageBitmap.Size = BitmapSize; + PageBitmap.Buffer = (uint8_t *)LargestFreeMemorySegment; + for (uint64_t i = 0; i < BitmapSize; i++) + *(uint8_t *)(PageBitmap.Buffer + i) = 0; + + this->ReservePages(0, MemorySize / PAGE_SIZE + 1); + for (uint64_t i = 0; i < MemoryMapSize; i++) + { + EFI_MEMORY_DESCRIPTOR *Descriptor = (EFI_MEMORY_DESCRIPTOR *)((uint64_t)memDesc + (i * DescriptorSize)); + if (Descriptor->Type == EfiConventionalMemory) + this->UnreservePages((void *)Descriptor->PhysicalStart, (Descriptor->NumberOfPages * 4096) / PAGE_SIZE + 1); + } + this->ReservePages(0, 0x100); // Reserve between 0 and 0x100000 + this->LockPages(PageBitmap.Buffer, PageBitmap.Size / PAGE_SIZE + 1); + } + + Physical::Physical() {} + Physical::~Physical() {} +} diff --git a/UEFI/src/Memory/VirtualMemoryManager.cpp b/UEFI/src/Memory/VirtualMemoryManager.cpp new file mode 100644 index 0000000..0cecf18 --- /dev/null +++ b/UEFI/src/Memory/VirtualMemoryManager.cpp @@ -0,0 +1,119 @@ +#include "memory.hpp" + +extern "C" void printf(const char* format, ...); + +void *memset(void *dest, int c, size_t n) +{ + unsigned int i; + for (i = 0; i < n; i++) + ((char *)dest)[i] = c; + return dest; +} + +namespace Memory +{ + void Virtual::Map(void *VirtualAddress, void *PhysicalAddress, uint64_t Flags) + { + if (!this->Table) + { + printf("No page table"); + return; + } + PageMapIndexer Index = PageMapIndexer((uint64_t)VirtualAddress); + PageDirectoryEntry PDE = this->Table->Entries[Index.PDP_i]; + PageTable *PDP; + if (!PDE.GetFlag(PTFlag::P)) + { + PDP = (PageTable *)KernelAllocator.RequestPage(); + memset(PDP, 0, PAGE_SIZE); + PDE.SetAddress((uint64_t)PDP >> 12); + PDE.SetFlag(PTFlag::P, true); + PDE.AddFlag(Flags); + this->Table->Entries[Index.PDP_i] = PDE; + } + else + PDP = (PageTable *)((uint64_t)PDE.GetAddress() << 12); + + PDE = PDP->Entries[Index.PD_i]; + PageTable *PD; + if (!PDE.GetFlag(PTFlag::P)) + { + PD = (PageTable *)KernelAllocator.RequestPage(); + memset(PD, 0, PAGE_SIZE); + PDE.SetAddress((uint64_t)PD >> 12); + PDE.SetFlag(PTFlag::P, true); + PDE.AddFlag(Flags); + PDP->Entries[Index.PD_i] = PDE; + } + else + PD = (PageTable *)((uint64_t)PDE.GetAddress() << 12); + + PDE = PD->Entries[Index.PT_i]; + PageTable *PT; + if (!PDE.GetFlag(PTFlag::P)) + { + PT = (PageTable *)KernelAllocator.RequestPage(); + memset(PT, 0, PAGE_SIZE); + PDE.SetAddress((uint64_t)PT >> 12); + PDE.SetFlag(PTFlag::P, true); + PDE.AddFlag(Flags); + PD->Entries[Index.PT_i] = PDE; + } + else + PT = (PageTable *)((uint64_t)PDE.GetAddress() << 12); + + PDE = PT->Entries[Index.P_i]; + PDE.SetAddress((uint64_t)PhysicalAddress >> 12); + PDE.SetFlag(PTFlag::P, true); + PDE.AddFlag(Flags); + PT->Entries[Index.P_i] = PDE; + __asm__ volatile("invlpg (%0)" + : + : "r"(VirtualAddress) + : "memory"); + } + + void Virtual::Map(void *VirtualAddress, void *PhysicalAddress, uint64_t PageCount, uint64_t Flags) + { + for (uint64_t i = 0; i < PageCount; i++) + this->Map((void *)((uint64_t)VirtualAddress + (i * PAGE_SIZE)), (void *)((uint64_t)PhysicalAddress + (i * PAGE_SIZE)), Flags); + } + + void Virtual::Unmap(void *VirtualAddress) + { + if (!this->Table) + { + printf("No page table"); + return; + } + + PageMapIndexer Index = PageMapIndexer((uint64_t)VirtualAddress); + PageDirectoryEntry PDE = this->Table->Entries[Index.PDP_i]; + PDE.ClearFlags(); + + __asm__ volatile("invlpg (%0)" + : + : "r"(VirtualAddress) + : "memory"); + } + + void Virtual::Unmap(void *VirtualAddress, uint64_t PageCount) + { + for (uint64_t i = 0; i < PageCount; i++) + this->Unmap((void *)((uint64_t)VirtualAddress + (i * PAGE_SIZE))); + } + + Virtual::Virtual(PageTable *Table) + { + uint64_t cr3; + __asm__ volatile("mov %%cr3, %0" + : "=r"(cr3)); + + if (Table) + this->Table = Table; + else + this->Table = (PageTable *)cr3; + } + + Virtual::~Virtual() {} +} diff --git a/UEFI/src/Memory/liballoc_1_1.c b/UEFI/src/Memory/liballoc_1_1.c new file mode 100644 index 0000000..f4f23a7 --- /dev/null +++ b/UEFI/src/Memory/liballoc_1_1.c @@ -0,0 +1,787 @@ +#include "liballoc_1_1.h" + +/** Durand's Amazing Super Duper Memory functions. */ + +#define VERSION "1.1" +#define ALIGNMENT 16ul // 4ul ///< This is the byte alignment that memory must be allocated on. IMPORTANT for GTK and other stuff. + +#define ALIGN_TYPE char /// unsigned char[16] /// unsigned short +#define ALIGN_INFO sizeof(ALIGN_TYPE) * 16 ///< Alignment information is stored right before the pointer. This is the number of bytes of information stored there. + +#define USE_CASE1 +#define USE_CASE2 +#define USE_CASE3 +#define USE_CASE4 +#define USE_CASE5 + +/** This macro will conveniently align our pointer upwards */ +#define ALIGN(ptr) \ + if (ALIGNMENT > 1) \ + { \ + uintptr_t diff; \ + ptr = (void *)((uintptr_t)ptr + ALIGN_INFO); \ + diff = (uintptr_t)ptr & (ALIGNMENT - 1); \ + if (diff != 0) \ + { \ + diff = ALIGNMENT - diff; \ + ptr = (void *)((uintptr_t)ptr + diff); \ + } \ + *((ALIGN_TYPE *)((uintptr_t)ptr - ALIGN_INFO)) = \ + diff + ALIGN_INFO; \ + } + +#define UNALIGN(ptr) \ + if (ALIGNMENT > 1) \ + { \ + uintptr_t diff = *((ALIGN_TYPE *)((uintptr_t)ptr - ALIGN_INFO)); \ + if (diff < (ALIGNMENT + ALIGN_INFO)) \ + { \ + ptr = (void *)((uintptr_t)ptr - diff); \ + } \ + } + +#define LIBALLOC_MAGIC 0xc001c0de +#define LIBALLOC_DEAD 0xdeaddead + +// #define LIBALLOCDEBUG 1 +#define LIBALLOCINFO 1 + +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + +// #define FLUSH() fflush(stdout) +#define FLUSH() +#define atexit(x) +#define printf(m, ...) + +#endif + +/** A structure found at the top of all system allocated + * memory blocks. It details the usage of the memory block. + */ +struct liballoc_major +{ + struct liballoc_major *prev; ///< Linked list information. + struct liballoc_major *next; ///< Linked list information. + unsigned int pages; ///< The number of pages in the block. + unsigned int size; ///< The number of pages in the block. + unsigned int usage; ///< The number of bytes used in the block. + struct liballoc_minor *first; ///< A pointer to the first allocated memory in the block. +}; + +/** This is a structure found at the beginning of all + * sections in a major block which were allocated by a + * malloc, calloc, realloc call. + */ +struct liballoc_minor +{ + struct liballoc_minor *prev; ///< Linked list information. + struct liballoc_minor *next; ///< Linked list information. + struct liballoc_major *block; ///< The owning block. A pointer to the major structure. + unsigned int magic; ///< A magic number to idenfity correctness. + unsigned int size; ///< The size of the memory allocated. Could be 1 byte or more. + unsigned int req_size; ///< The size of memory requested. +}; + +static struct liballoc_major *l_memRoot = NULL; ///< The root memory block acquired from the system. +static struct liballoc_major *l_bestBet = NULL; ///< The major with the most free memory. + +static unsigned int l_pageSize = 4096; ///< The size of an individual page. Set up in liballoc_init. +static unsigned int l_pageCount = 16; ///< The number of pages to request per chunk. Set up in liballoc_init. +static unsigned long long l_allocated = 0; ///< Running total of allocated memory. +static unsigned long long l_inuse = 0; ///< Running total of used memory. + +static long long l_warningCount = 0; ///< Number of warnings encountered +static long long l_errorCount = 0; ///< Number of actual errors +static long long l_possibleOverruns = 0; ///< Number of possible overruns + +// *********** HELPER FUNCTIONS ******************************* + +static void *liballoc_memset(void *s, int c, size_t n) +{ + unsigned int i; + for (i = 0; i < n; i++) + ((char *)s)[i] = c; + + return s; +} +static void *liballoc_memcpy(void *s1, const void *s2, size_t n) +{ + char *cdest; + char *csrc; + unsigned int *ldest = (unsigned int *)s1; + unsigned int *lsrc = (unsigned int *)s2; + + while (n >= sizeof(unsigned int)) + { + *ldest++ = *lsrc++; + n -= sizeof(unsigned int); + } + + cdest = (char *)ldest; + csrc = (char *)lsrc; + + while (n > 0) + { + *cdest++ = *csrc++; + n -= 1; + } + + return s1; +} + +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO +static void liballoc_dump() +{ +#ifdef LIBALLOCDEBUG + struct liballoc_major *maj = l_memRoot; + struct liballoc_minor *min = NULL; +#endif + + printf("liballoc: ------ Memory data ---------------\n"); + printf("liballoc: System memory allocated: %i bytes\n", l_allocated); + printf("liballoc: Memory in used (malloc'ed): %i bytes\n", l_inuse); + printf("liballoc: Warning count: %i\n", l_warningCount); + printf("liballoc: Error count: %i\n", l_errorCount); + printf("liballoc: Possible overruns: %i\n", l_possibleOverruns); + +#ifdef LIBALLOCDEBUG + while (maj != NULL) + { + printf("liballoc: %x: total = %i, used = %i\n", + maj, + maj->size, + maj->usage); + + min = maj->first; + while (min != NULL) + { + printf("liballoc: %x: %i bytes\n", + min, + min->size); + min = min->next; + } + + maj = maj->next; + } +#endif + + FLUSH(); +} +#endif + +// *************************************************************** + +static struct liballoc_major *allocate_new_page(unsigned int size) +{ + unsigned int st; + struct liballoc_major *maj; + + // This is how much space is required. + st = size + sizeof(struct liballoc_major); + st += sizeof(struct liballoc_minor); + + // Perfect amount of space? + if ((st % l_pageSize) == 0) + st = st / (l_pageSize); + else + st = st / (l_pageSize) + 1; + // No, add the buffer. + + // Make sure it's >= the minimum size. + if (st < l_pageCount) + st = l_pageCount; + + maj = (struct liballoc_major *)liballoc_alloc(st); + + if (maj == NULL) + { + l_warningCount += 1; +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: WARNING: liballoc_alloc( %i ) return NULL\n", st); + FLUSH(); +#endif + return NULL; // uh oh, we ran out of memory. + } + + maj->prev = NULL; + maj->next = NULL; + maj->pages = st; + maj->size = st * l_pageSize; + maj->usage = sizeof(struct liballoc_major); + maj->first = NULL; + + l_allocated += maj->size; + +#ifdef LIBALLOCDEBUG + printf("liballoc: Resource allocated %x of %i pages (%i bytes) for %i size.\n", maj, st, maj->size, size); + + printf("liballoc: Total memory usage = %i KB\n", (int)((l_allocated / (1024)))); + FLUSH(); +#endif + + return maj; +} + +void *PREFIX(malloc)(size_t req_size) +{ + int startedBet = 0; + unsigned long long bestSize = 0; + void *p = NULL; + uintptr_t diff; + struct liballoc_major *maj; + struct liballoc_minor *min; + struct liballoc_minor *new_min; + unsigned long size = req_size; + + // For alignment, we adjust size so there's enough space to align. + if (ALIGNMENT > 1) + { + size += ALIGNMENT + ALIGN_INFO; + } + // So, ideally, we really want an alignment of 0 or 1 in order + // to save space. + + liballoc_lock(); + + if (size == 0) + { + l_warningCount += 1; +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: WARNING: alloc( 0 ) called from %x\n", + __builtin_return_address(0)); + FLUSH(); +#endif + liballoc_unlock(); + return PREFIX(malloc)(1); + } + + if (l_memRoot == NULL) + { +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO +#ifdef LIBALLOCDEBUG + printf("liballoc: initialization of liballoc " VERSION "\n"); +#endif + atexit(liballoc_dump); + FLUSH(); +#endif + + // This is the first time we are being used. + l_memRoot = allocate_new_page(size); + if (l_memRoot == NULL) + { + liballoc_unlock(); +#ifdef LIBALLOCDEBUG + printf("liballoc: initial l_memRoot initialization failed\n", p); + FLUSH(); +#endif + return NULL; + } + +#ifdef LIBALLOCDEBUG + printf("liballoc: set up first memory major %x\n", l_memRoot); + FLUSH(); +#endif + } + +#ifdef LIBALLOCDEBUG + printf("liballoc: %x PREFIX(malloc)( %i ): ", + __builtin_return_address(0), + size); + FLUSH(); +#endif + + // Now we need to bounce through every major and find enough space.... + + maj = l_memRoot; + startedBet = 0; + + // Start at the best bet.... + if (l_bestBet != NULL) + { + bestSize = l_bestBet->size - l_bestBet->usage; + + if (bestSize > (size + sizeof(struct liballoc_minor))) + { + maj = l_bestBet; + startedBet = 1; + } + } + + while (maj != NULL) + { + diff = maj->size - maj->usage; + // free memory in the block + + if (bestSize < diff) + { + // Hmm.. this one has more memory then our bestBet. Remember! + l_bestBet = maj; + bestSize = diff; + } + +#ifdef USE_CASE1 + + // CASE 1: There is not enough space in this major block. + if (diff < (size + sizeof(struct liballoc_minor))) + { +#ifdef LIBALLOCDEBUG + printf("CASE 1: Insufficient space in block %x\n", maj); + FLUSH(); +#endif + + // Another major block next to this one? + if (maj->next != NULL) + { + maj = maj->next; // Hop to that one. + continue; + } + + if (startedBet == 1) // If we started at the best bet, + { // let's start all over again. + maj = l_memRoot; + startedBet = 0; + continue; + } + + // Create a new major block next to this one and... + maj->next = allocate_new_page(size); // next one will be okay. + if (maj->next == NULL) + break; // no more memory. + maj->next->prev = maj; + maj = maj->next; + + // .. fall through to CASE 2 .. + } + +#endif + +#ifdef USE_CASE2 + + // CASE 2: It's a brand new block. + if (maj->first == NULL) + { + maj->first = (struct liballoc_minor *)((uintptr_t)maj + sizeof(struct liballoc_major)); + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->next = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof(struct liballoc_minor); + + l_inuse += size; + + p = (void *)((uintptr_t)(maj->first) + sizeof(struct liballoc_minor)); + + ALIGN(p); + +#ifdef LIBALLOCDEBUG + printf("CASE 2: returning %x\n", p); + FLUSH(); +#endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + +#ifdef USE_CASE3 + + // CASE 3: Block in use and enough space at the start of the block. + diff = (uintptr_t)(maj->first); + diff -= (uintptr_t)maj; + diff -= sizeof(struct liballoc_major); + + if (diff >= (size + sizeof(struct liballoc_minor))) + { + // Yes, space in front. Squeeze in. + maj->first->prev = (struct liballoc_minor *)((uintptr_t)maj + sizeof(struct liballoc_major)); + maj->first->prev->next = maj->first; + maj->first = maj->first->prev; + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof(struct liballoc_minor); + + l_inuse += size; + + p = (void *)((uintptr_t)(maj->first) + sizeof(struct liballoc_minor)); + ALIGN(p); + +#ifdef LIBALLOCDEBUG + printf("CASE 3: returning %x\n", p); + FLUSH(); +#endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + +#ifdef USE_CASE4 + + // CASE 4: There is enough space in this block. But is it contiguous? + min = maj->first; + + // Looping within the block now... + while (min != NULL) + { + // CASE 4.1: End of minors in a block. Space from last and end? + if (min->next == NULL) + { + // the rest of this block is free... is it big enough? + diff = (uintptr_t)(maj) + maj->size; + diff -= (uintptr_t)min; + diff -= sizeof(struct liballoc_minor); + diff -= min->size; + // minus already existing usage.. + + if (diff >= (size + sizeof(struct liballoc_minor))) + { + // yay.... + min->next = (struct liballoc_minor *)((uintptr_t)min + sizeof(struct liballoc_minor) + min->size); + min->next->prev = min; + min = min->next; + min->next = NULL; + min->magic = LIBALLOC_MAGIC; + min->block = maj; + min->size = size; + min->req_size = req_size; + maj->usage += size + sizeof(struct liballoc_minor); + + l_inuse += size; + + p = (void *)((uintptr_t)min + sizeof(struct liballoc_minor)); + ALIGN(p); + +#ifdef LIBALLOCDEBUG + printf("CASE 4.1: returning %x\n", p); + FLUSH(); +#endif + liballoc_unlock(); // release the lock + return p; + } + } + + // CASE 4.2: Is there space between two minors? + if (min->next != NULL) + { + // is the difference between here and next big enough? + diff = (uintptr_t)(min->next); + diff -= (uintptr_t)min; + diff -= sizeof(struct liballoc_minor); + diff -= min->size; + // minus our existing usage. + + if (diff >= (size + sizeof(struct liballoc_minor))) + { + // yay...... + new_min = (struct liballoc_minor *)((uintptr_t)min + sizeof(struct liballoc_minor) + min->size); + + new_min->magic = LIBALLOC_MAGIC; + new_min->next = min->next; + new_min->prev = min; + new_min->size = size; + new_min->req_size = req_size; + new_min->block = maj; + min->next->prev = new_min; + min->next = new_min; + maj->usage += size + sizeof(struct liballoc_minor); + + l_inuse += size; + + p = (void *)((uintptr_t)new_min + sizeof(struct liballoc_minor)); + ALIGN(p); + +#ifdef LIBALLOCDEBUG + printf("CASE 4.2: returning %x\n", p); + FLUSH(); +#endif + + liballoc_unlock(); // release the lock + return p; + } + } // min->next != NULL + + min = min->next; + } // while min != NULL ... + +#endif + +#ifdef USE_CASE5 + + // CASE 5: Block full! Ensure next block and loop. + if (maj->next == NULL) + { +#ifdef LIBALLOCDEBUG + printf("CASE 5: block full\n"); + FLUSH(); +#endif + + if (startedBet == 1) + { + maj = l_memRoot; + startedBet = 0; + continue; + } + + // we've run out. we need more... + maj->next = allocate_new_page(size); // next one guaranteed to be okay + if (maj->next == NULL) + break; // uh oh, no more memory..... + maj->next->prev = maj; + } + +#endif + + maj = maj->next; + } // while (maj != NULL) + + liballoc_unlock(); // release the lock + +#ifdef LIBALLOCDEBUG + printf("All cases exhausted. No memory available.\n"); + FLUSH(); +#endif +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: WARNING: PREFIX(malloc)( %i ) returning NULL.\n", size); + liballoc_dump(); + FLUSH(); +#endif + return NULL; +} + +void PREFIX(free)(void *ptr) +{ + struct liballoc_minor *min; + struct liballoc_major *maj; + + if (ptr == NULL) + { + l_warningCount += 1; +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: WARNING: PREFIX(free)( NULL ) called from %x\n", + __builtin_return_address(0)); + FLUSH(); +#endif + return; + } + + UNALIGN(ptr); + + liballoc_lock(); // lockit + + min = (struct liballoc_minor *)((uintptr_t)ptr - sizeof(struct liballoc_minor)); + + if (min->magic != LIBALLOC_MAGIC) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF))) + { + l_possibleOverruns += 1; +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC); + FLUSH(); +#endif + } + + if (min->magic == LIBALLOC_DEAD) + { +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0)); + FLUSH(); +#endif + } + else + { +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0)); + FLUSH(); +#endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return; + } + +#ifdef LIBALLOCDEBUG + printf("liballoc: %x PREFIX(free)( %x ): ", + __builtin_return_address(0), + ptr); + FLUSH(); +#endif + + maj = min->block; + + l_inuse -= min->size; + + maj->usage -= (min->size + sizeof(struct liballoc_minor)); + min->magic = LIBALLOC_DEAD; // No mojo. + + if (min->next != NULL) + min->next->prev = min->prev; + if (min->prev != NULL) + min->prev->next = min->next; + + if (min->prev == NULL) + maj->first = min->next; + // Might empty the block. This was the first + // minor. + + // We need to clean up after the majors now.... + + if (maj->first == NULL) // Block completely unused. + { + if (l_memRoot == maj) + l_memRoot = maj->next; + if (l_bestBet == maj) + l_bestBet = NULL; + if (maj->prev != NULL) + maj->prev->next = maj->next; + if (maj->next != NULL) + maj->next->prev = maj->prev; + l_allocated -= maj->size; + + liballoc_free(maj, maj->pages); + } + else + { + if (l_bestBet != NULL) + { + int bestSize = l_bestBet->size - l_bestBet->usage; + int majSize = maj->size - maj->usage; + + if (majSize > bestSize) + l_bestBet = maj; + } + } + +#ifdef LIBALLOCDEBUG + printf("OK\n"); + FLUSH(); +#endif + + liballoc_unlock(); // release the lock +} + +void *PREFIX(calloc)(size_t nobj, size_t size) +{ + int real_size; + void *p; + + real_size = nobj * size; + + p = PREFIX(malloc)(real_size); + + liballoc_memset(p, 0, real_size); + + return p; +} + +void *PREFIX(realloc)(void *p, size_t size) +{ + void *ptr; + struct liballoc_minor *min; + unsigned int real_size; + + // Honour the case of size == 0 => free old and return NULL + if (size == 0) + { + PREFIX(free) + (p); + return NULL; + } + + // In the case of a NULL pointer, return a simple malloc. + if (p == NULL) + return PREFIX(malloc)(size); + + // Unalign the pointer if required. + ptr = p; + UNALIGN(ptr); + + liballoc_lock(); // lockit + + min = (struct liballoc_minor *)((uintptr_t)ptr - sizeof(struct liballoc_minor)); + + // Ensure it is a valid structure. + if (min->magic != LIBALLOC_MAGIC) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF))) + { + l_possibleOverruns += 1; +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC); + FLUSH(); +#endif + } + + if (min->magic == LIBALLOC_DEAD) + { +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0)); + FLUSH(); +#endif + } + else + { +#if defined LIBALLOCDEBUG || defined LIBALLOCINFO + printf("liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0)); + FLUSH(); +#endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return NULL; + } + + // Definitely a memory block. + + real_size = min->req_size; + + if (real_size >= size) + { + min->req_size = size; + liballoc_unlock(); + return p; + } + + liballoc_unlock(); + + // If we got here then we're reallocating to a block bigger than us. + ptr = PREFIX(malloc)(size); // We need to allocate new memory + liballoc_memcpy(ptr, p, real_size); + PREFIX(free) + (p); + + return ptr; +} diff --git a/UEFI/src/Memory/liballoc_1_1.h b/UEFI/src/Memory/liballoc_1_1.h new file mode 100644 index 0000000..0406504 --- /dev/null +++ b/UEFI/src/Memory/liballoc_1_1.h @@ -0,0 +1,90 @@ +#ifndef _LIBALLOC_H +#define _LIBALLOC_H + +#ifndef LYNX_LIBALLOC_TYPES_H +#define LYNX_LIBALLOC_TYPES_H + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define ALIGN_UP(x, align) ((__typeof__(x))(((uint64_t)(x) + ((align)-1)) & (~((align)-1)))) +#define ALIGN_DOWN(x, align) ((__typeof__(x))((x) & (~((align)-1)))) + +#endif // !LYNX_LIBALLOC_TYPES_H + +/** \defgroup ALLOCHOOKS liballoc hooks + * + * These are the OS specific functions which need to + * be implemented on any platform that the library + * is expected to work on. + */ + +/** @{ */ + +// If we are told to not define our own size_t, then we skip the define. +//#define _HAVE_UINTPTR_T +// typedef unsigned long uintptr_t; + +// This lets you prefix malloc and friends +#define PREFIX(func) kliballoc_##func + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** This function is supposed to lock the memory data structures. It + * could be as simple as disabling interrupts or acquiring a spinlock. + * It's up to you to decide. + * + * \return 0 if the lock was acquired successfully. Anything else is + * failure. + */ + extern int liballoc_lock(); + + /** This function unlocks what was previously locked by the liballoc_lock + * function. If it disabled interrupts, it enables interrupts. If it + * had acquiried a spinlock, it releases the spinlock. etc. + * + * \return 0 if the lock was successfully released. + */ + extern int liballoc_unlock(); + + /** This is the hook into the local system which allocates pages. It + * accepts an integer parameter which is the number of pages + * required. The page size was set up in the liballoc_init function. + * + * \return NULL if the pages were not allocated. + * \return A pointer to the allocated memory. + */ + extern void *liballoc_alloc(size_t); + + /** This frees previously allocated memory. The void* parameter passed + * to the function is the exact same value returned from a previous + * liballoc_alloc call. + * + * The integer value is the number of pages to free. + * + * \return 0 if the memory was successfully freed. + */ + extern int liballoc_free(void *, size_t); + + extern void *PREFIX(malloc)(size_t); ///< The standard function. + extern void *PREFIX(realloc)(void *, size_t); ///< The standard function. + extern void *PREFIX(calloc)(size_t, size_t); ///< The standard function. + extern void PREFIX(free)(void *); ///< The standard function. + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif diff --git a/UEFI/src/Memory/memory.hpp b/UEFI/src/Memory/memory.hpp new file mode 100644 index 0000000..8e2dd57 --- /dev/null +++ b/UEFI/src/Memory/memory.hpp @@ -0,0 +1,433 @@ +#ifndef __FENNIX_KERNEL_INTERNAL_MEMORY_H__ +#define __FENNIX_KERNEL_INTERNAL_MEMORY_H__ + +#ifndef LYNX_MEMORY_TYPES_H +#define LYNX_MEMORY_TYPES_H + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#define ALIGN_UP(x, align) ((__typeof__(x))(((uint64_t)(x) + ((align)-1)) & (~((align)-1)))) +#define ALIGN_DOWN(x, align) ((__typeof__(x))((x) & (~((align)-1)))) + +#endif // !LYNX_MEMORY_TYPES_H + +#include "../bitmap.hpp" + +#include +#include + +#define PAGE_SIZE 0x1000 + +// to pages +#define TO_PAGES(d) (d / PAGE_SIZE + 1) +// from pages +#define FROM_PAGES(d) (d * PAGE_SIZE - 1) + +#define NORMAL_VMA_OFFSET 0xFFFF800000000000 +#define KERNEL_VMA_OFFSET 0xFFFFFFFF80000000 + +/** + * @brief KERNEL_HEAP_BASE is the base address of the kernel heap + */ +#define KERNEL_HEAP_BASE 0xFFFFC00000000000 +/** + * @brief USER_HEAP_BASE is the base address of the user heap allocated by the kernel + */ +#define USER_HEAP_BASE 0xFFFFD00000000000 + +#ifdef __cplusplus + +namespace Memory +{ + /** + * @brief https://wiki.osdev.org/images/4/41/64-bit_page_tables1.png + * @brief https://wiki.osdev.org/images/6/6b/64-bit_page_tables2.png + */ + enum PTFlag + { + /** @brief Present */ + P = 1 << 0, + + /** @brief Read/Write */ + RW = 1 << 1, + + /** @brief User/Supervisor */ + US = 1 << 2, + + /** @brief Write-Through */ + PWT = 1 << 3, + + /** @brief Cache Disable */ + PCD = 1 << 4, + + /** @brief Accessed */ + A = 1 << 5, + + /** @brief Dirty */ + D = 1 << 6, + + /** @brief Page Size */ + PS = 1 << 7, + + /** @brief Global */ + G = 1 << 8, + + /** @brief Available 0 */ + AVL0 = 1 << 9, + + /** @brief Available 1 */ + AVL1 = 1 << 10, + + /** @brief Available 2 */ + AVL2 = 1 << 11, + + /** @brief Page Attribute Table */ + PAT = 1 << 12, + + /** @brief Available 3 */ + AVL3 = (uint64_t)1 << 52, + + /** @brief Available 4 */ + AVL4 = (uint64_t)1 << 53, + + /** @brief Available 5 */ + AVL5 = (uint64_t)1 << 54, + + /** @brief Available 6 */ + AVL6 = (uint64_t)1 << 55, + + /** @brief Available 7 */ + AVL7 = (uint64_t)1 << 56, + + /** @brief Available 8 */ + AVL8 = (uint64_t)1 << 57, + + /** @brief Available 9 */ + AVL9 = (uint64_t)1 << 58, + + /** @brief Protection Key 0 */ + PK0 = (uint64_t)1 << 59, + + /** @brief Protection Key 1 */ + PK1 = (uint64_t)1 << 60, + + /** @brief Protection Key 2 */ + PK2 = (uint64_t)1 << 61, + + /** @brief Protection Key 3 */ + PK3 = (uint64_t)1 << 62, + + /** @brief Execute Disable */ + XD = (uint64_t)1 << 63 + }; + + typedef union __attribute__((packed)) + { + struct + { + bool Present : 1; + bool ReadWrite : 1; + bool UserSupervisor : 1; + bool WriteThrough : 1; + bool CacheDisable : 1; + bool Accessed : 1; + bool Dirty : 1; + bool PageSize : 1; + bool Global : 1; + uint8_t Available1 : 3; + bool PageAttributeTable : 1; + uint64_t Reserved : 39; + uint32_t Available2 : 7; + uint16_t ProtectionKey : 4; + bool ExecuteDisable : 1; + }; + uint64_t raw; + } PDEData; + + struct __attribute__((packed)) PageDirectoryEntry + { + PDEData Value; + void AddFlag(uint64_t Flag) { this->Value.raw |= Flag; } + void RemoveFlags(uint64_t Flag) { this->Value.raw &= ~Flag; } + void ClearFlags() { this->Value.raw = 0; } + void SetFlag(uint64_t Flag, bool Enabled) + { + this->Value.raw &= ~Flag; + if (Enabled) + this->Value.raw |= Flag; + } + bool GetFlag(uint64_t Flag) { return (this->Value.raw & Flag) > 0 ? true : false; } + uint64_t GetFlag() { return this->Value.raw; } + void SetAddress(uint64_t Address) + { +#if defined(__amd64__) + Address &= 0x000000FFFFFFFFFF; + this->Value.raw &= 0xFFF0000000000FFF; + this->Value.raw |= (Address << 12); +#elif defined(__i386__) + Address &= 0x000FFFFF; + this->Value.raw &= 0xFFC00003; + this->Value.raw |= (Address << 12); +#elif defined(__aarch64__) + Address &= 0x000000FFFFFFFFFF; + this->Value.raw &= 0xFFF0000000000FFF; + this->Value.raw |= (Address << 12); +#endif + } + uint64_t GetAddress() + { +#if defined(__amd64__) + return (this->Value.raw & 0x000FFFFFFFFFF000) >> 12; +#elif defined(__i386__) + return (this->Value.raw & 0x003FFFFF000) >> 12; +#elif defined(__aarch64__) + return (this->Value.raw & 0x000FFFFFFFFFF000) >> 12; +#endif + } + }; + + struct PageTable + { + PageDirectoryEntry Entries[512]; + } __attribute__((aligned(0x1000))); + + class Physical + { + private: + uint64_t TotalMemory = 0; + uint64_t FreeMemory = 0; + uint64_t ReservedMemory = 0; + uint64_t UsedMemory = 0; + uint64_t PageBitmapIndex = 0; + Bitmap PageBitmap; + + void ReservePage(void *Address); + void ReservePages(void *Address, uint64_t PageCount); + void UnreservePage(void *Address); + void UnreservePages(void *Address, uint64_t PageCount); + + public: + /** + * @brief Get Total Memory + * + * @return uint64_t + */ + uint64_t GetTotalMemory(); + /** + * @brief Get Free Memory + * + * @return uint64_t + */ + uint64_t GetFreeMemory(); + /** + * @brief Get Reserved Memory + * + * @return uint64_t + */ + uint64_t GetReservedMemory(); + /** + * @brief Get Used Memory + * + * @return uint64_t + */ + uint64_t GetUsedMemory(); + + /** + * @brief Swap page + * + * @param Address Address of the page + * @return true if swap was successful + * @return false if swap was unsuccessful + */ + bool SwapPage(void *Address); + /** + * @brief Swap pages + * + * @param Address Address of the pages + * @param PageCount Number of pages + * @return true if swap was successful + * @return false if swap was unsuccessful + */ + bool SwapPages(void *Address, uint64_t PageCount); + /** + * @brief Unswap page + * + * @param Address Address of the page + * @return true if unswap was successful + * @return false if unswap was unsuccessful + */ + bool UnswapPage(void *Address); + /** + * @brief Unswap pages + * + * @param Address Address of the pages + * @param PageCount Number of pages + * @return true if unswap was successful + * @return false if unswap was unsuccessful + */ + bool UnswapPages(void *Address, uint64_t PageCount); + + /** + * @brief Lock page + * + * @param Address Address of the page + */ + void LockPage(void *Address); + /** + * @brief Lock pages + * + * @param Address Address of the pages + * @param PageCount Number of pages + */ + void LockPages(void *Address, uint64_t PageCount); + + /** + * @brief Request page + * + * @return void* Allocated page address + */ + void *RequestPage(); + /** + * @brief Request pages + * + * @param PageCount Number of pages + * @return void* Allocated pages address + */ + void *RequestPages(uint64_t Count); + /** + * @brief Free page + * + * @param Address Address of the page + */ + void FreePage(void *Address); + /** + * @brief Free pages + * + * @param Address Address of the pages + * @param PageCount Number of pages + */ + void FreePages(void *Address, uint64_t Count); + /** @brief Do not use. */ + void Init(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable); + /** @brief Do not use. */ + Physical(); + /** @brief Do not use. */ + ~Physical(); + }; + + class Virtual + { + private: + PageTable *Table = nullptr; + + class PageMapIndexer + { + public: + uint64_t PDP_i; + uint64_t PD_i; + uint64_t PT_i; + uint64_t P_i; + + PageMapIndexer(uint64_t VirtualAddress) + { +#if defined(__amd64__) + PDP_i = (VirtualAddress & ((uint64_t)0x1FF << 39)) >> 39; + PD_i = (VirtualAddress & ((uint64_t)0x1FF << 30)) >> 30; + PT_i = (VirtualAddress & ((uint64_t)0x1FF << 21)) >> 21; + P_i = (VirtualAddress & ((uint64_t)0x1FF << 12)) >> 12; +#elif defined(__i386__) + PD_i = (VirtualAddress & ((uint64_t)0x3FF << 22)) >> 22; + PT_i = (VirtualAddress & ((uint64_t)0x3FF << 12)) >> 12; + P_i = (VirtualAddress & ((uint64_t)0xFFF << 0)) >> 0; +#elif defined(__aarch64__) + PD_i = (VirtualAddress & ((uint64_t)0x1FF << 30)) >> 30; + PT_i = (VirtualAddress & ((uint64_t)0x1FF << 21)) >> 21; + P_i = (VirtualAddress & ((uint64_t)0x1FF << 12)) >> 12; +#endif + } + }; + + public: + /** + * @brief Map page. + * + * @param VirtualAddress Virtual address of the page. + * @param PhysicalAddress Physical address of the page. + * @param Flags Flags of the page. Check PTFlag enum. + */ + void Map(void *VirtualAddress, void *PhysicalAddress, uint64_t Flags); + /** + * @brief Map multiple pages. + * + * @param VirtualAddress First virtual address of the page. + * @param PhysicalAddress First physical address of the page. + * @param PageCount Number of pages. + * @param Flags Flags of the page. Check PTFlag enum. + */ + void Map(void *VirtualAddress, void *PhysicalAddress, uint64_t PageCount, uint64_t Flags); + /** + * @brief Unmap page. + * + * @param VirtualAddress Virtual address of the page. + */ + void Unmap(void *VirtualAddress); + /** + * @brief Unmap multiple pages. + * + * @param VirtualAddress First virtual address of the page. + * @param PageCount Number of pages. + */ + void Unmap(void *VirtualAddress, uint64_t PageCount); + /** + * @brief Construct a new Virtual object + * + * @param Table Page table. If null, it will use the current page table. + */ + Virtual(PageTable *Table = nullptr); + /** + * @brief Destroy the Virtual object + * + */ + ~Virtual(); + }; +} + +void *operator new(uint64_t Size); +void *operator new[](uint64_t Size); +void operator delete(void *Pointer); +void operator delete[](void *Pointer); +void operator delete(void *Pointer, long unsigned int Size); +void operator delete[](void *Pointer, long unsigned int Size); + +extern Memory::Physical KernelAllocator; +extern Memory::PageTable *KernelPageTable; + +#endif // __cplusplus + +EXTERNC void InitializeMemoryManagement(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable); + +EXTERNC void *HeapMalloc(uint64_t Size); +EXTERNC void *HeapCalloc(uint64_t n, uint64_t Size); +EXTERNC void *HeapRealloc(void *Address, uint64_t Size); +EXTERNC void HeapFree(void *Address); + +#define kmalloc(Size) HeapMalloc(Size) +#define kcalloc(n, Size) HeapCalloc(n, Size) +#define krealloc(Address, Size) HeapRealloc(Address, Size) +#define kfree(Address) HeapFree(Address) + +#endif // !__FENNIX_KERNEL_INTERNAL_MEMORY_H__ diff --git a/UEFI/src/Paging.c b/UEFI/src/Paging.c deleted file mode 100644 index e69de29..0000000 diff --git a/UEFI/src/VirtualMemory.c b/UEFI/src/VirtualMemory.c deleted file mode 100644 index e69de29..0000000 diff --git a/UEFI/src/bitmap.hpp b/UEFI/src/bitmap.hpp new file mode 100644 index 0000000..3e9509b --- /dev/null +++ b/UEFI/src/bitmap.hpp @@ -0,0 +1,33 @@ +#pragma once + +#ifndef LYNX_BITMAP_TYPES_H +#define LYNX_BITMAP_TYPES_H + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define ALIGN_UP(x, align) ((__typeof__(x))(((uint64_t)(x) + ((align)-1)) & (~((align)-1)))) +#define ALIGN_DOWN(x, align) ((__typeof__(x))((x) & (~((align)-1)))) + +#endif // !LYNX_BITMAP_TYPES_H + +#ifdef __cplusplus + +class Bitmap +{ +public: + size_t Size; + uint8_t *Buffer; + bool operator[](uint64_t index); + bool Set(uint64_t index, bool value); + bool Get(uint64_t index); +}; + +#endif // __cplusplus diff --git a/UEFI/src/cxxabi.cpp b/UEFI/src/cxxabi.cpp new file mode 100644 index 0000000..588d2d3 --- /dev/null +++ b/UEFI/src/cxxabi.cpp @@ -0,0 +1,157 @@ +#ifndef LYNX_CXXABI_TYPES_H +#define LYNX_CXXABI_TYPES_H + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define ALIGN_UP(x, align) ((__typeof__(x))(((uint64_t)(x) + ((align)-1)) & (~((align)-1)))) +#define ALIGN_DOWN(x, align) ((__typeof__(x))((x) & (~((align)-1)))) + +#endif // !LYNX_CXXABI_TYPES_H + +extern "C" void printf(const char *format, ...); + +// TODO: complete implementation for everything +// TODO: https://wiki.osdev.org/C%2B%2B + +#define ATEXIT_MAX_FUNCS 128 + +typedef unsigned uarch_t; + +struct atexit_func_entry_t +{ + /* + * Each member is at least 4 bytes large. Such that each entry is 12bytes. + * 128 * 12 = 1.5KB exact. + **/ + void (*destructor_func)(void *); + void *obj_ptr; + void *dso_handle; +}; + +typedef enum +{ + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8 +} _Unwind_Reason_Code; + +struct _Unwind_Context; +typedef unsigned _Unwind_Exception_Class __attribute__((__mode__(__DI__))); +typedef unsigned _Unwind_Word __attribute__((__mode__(__unwind_word__))); +typedef void (*_Unwind_Exception_Cleanup_Fn)(_Unwind_Reason_Code, struct _Unwind_Exception *); +typedef int _Unwind_Action; + +struct _Unwind_Exception +{ + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; +#if !defined(__USING_SJLJ_EXCEPTIONS__) && defined(__SEH__) + _Unwind_Word private_[6]; +#else + _Unwind_Word private_1; + _Unwind_Word private_2; +#endif +} __attribute__((__aligned__)); + +extern void *__dso_handle = 0; +atexit_func_entry_t __atexit_funcs[ATEXIT_MAX_FUNCS]; +uarch_t __atexit_func_count = 0; + +extern "C" int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) +{ + printf("__cxa_atexit( %p %p %p ) triggered.", f, objptr, dso); + if (__atexit_func_count >= ATEXIT_MAX_FUNCS) + return -1; + __atexit_funcs[__atexit_func_count].destructor_func = f; + __atexit_funcs[__atexit_func_count].obj_ptr = objptr; + __atexit_funcs[__atexit_func_count].dso_handle = dso; + __atexit_func_count++; + return 0; +} + +extern "C" void __cxa_finalize(void *f) +{ + printf("__cxa_finalize( %p ) triggered.", f); + uarch_t i = __atexit_func_count; + if (!f) + { + while (i--) + if (__atexit_funcs[i].destructor_func) + (*__atexit_funcs[i].destructor_func)(__atexit_funcs[i].obj_ptr); + + return; + } + + while (i--) + if (__atexit_funcs[i].destructor_func == f) + { + (*__atexit_funcs[i].destructor_func)(__atexit_funcs[i].obj_ptr); + __atexit_funcs[i].destructor_func = 0; + } +} + +extern "C" _Unwind_Reason_Code __gxx_personality_v0(int version, _Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception *ue_header, _Unwind_Context *context) +{ + printf("__gxx_personality_v0( %d %p %p %p %p ) triggered.", version, actions, exception_class, ue_header, context); + return _URC_NO_REASON; +} + +extern "C" void _Unwind_Resume(struct _Unwind_Exception *exc) { printf("_Unwind_Resume( %p ) triggered.", exc); } + +extern "C" void *__cxa_allocate_exception(uint64_t thrown_size) throw() +{ + printf("__cxa_allocate_exception( %#llu ) triggered.", thrown_size); + return (void *)0; +} + +extern "C" void __cxa_throw(void *thrown_object, void *tinfo, void (*dest)(void *)) { printf("__cxa_throw( %p %p %p ) triggered.", thrown_object, tinfo, dest); } + +extern "C" void __cxa_rethrow() { printf("__cxa_rethrow() triggered."); } + +extern "C" void __cxa_pure_virtual() { printf("__cxa_pure_virtual() triggered."); } + +extern "C" void __cxa_throw_bad_array_new_length() { printf("__cxa_throw_bad_array_new_length() triggered."); } + +extern "C" void __cxa_free_exception(void *thrown_exception) { printf("__cxa_free_exception( %p ) triggered.", thrown_exception); } + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +extern "C" void *__cxa_begin_catch(void *e) throw() +#else +extern "C" void *__cxa_begin_catch(void *e) +#endif +{ + printf("__cxa_begin_catch( %p ) triggered.", e); + return (void *)0; +} + +extern "C" void __cxa_end_catch() { printf("__cxa_end_catch() triggered."); } + +__extension__ typedef int __guard __attribute__((mode(__DI__))); + +extern "C" int __cxa_guard_acquire(__guard *g) +{ + printf("__cxa_guard_acquire( %p ) triggered.", g); + return !*(char *)(g); +} + +extern "C" void __cxa_guard_release(__guard *g) +{ + printf("__cxa_guard_release( %p ) triggered.", g); + *(char *)g = 1; +} + +extern "C" void __cxa_guard_abort(__guard *g) { printf("__cxa_guard_abort( %p ) triggered.", g); } diff --git a/UEFI/src/printf.c b/UEFI/src/printf.c new file mode 100644 index 0000000..7ea4206 --- /dev/null +++ b/UEFI/src/printf.c @@ -0,0 +1,1606 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2022, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. The original code for exponential specifiers was + * contributed by Martijn Jasperse . + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with + * a very limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma GCC diagnostic ignored "-Wtype-limits" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wtautological-compare" +#pragma GCC diagnostic ignored "-Wsign-compare" + +typedef __INT8_TYPE__ int8_t; +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; + +typedef __INT_FAST64_TYPE__ int_fast64_t; +typedef __INTMAX_TYPE__ intmax_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#define bool int +#define true 1 +#define false 0 + +#define INT_MAX __INT_MAX__ + +// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +#if PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +#include "printf.h" + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#define printf_ printf +#define sprintf_ sprintf +#define vsprintf_ vsprintf +#define snprintf_ snprintf +#define vsnprintf_ vsnprintf +#define vprintf_ vprintf +#endif + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +#ifndef PRINTF_INTEGER_BUFFER_SIZE +#define PRINTF_INTEGER_BUFFER_SIZE 32 +#endif + +// size of the fixed (on-stack) buffer for printing individual decimal numbers. +// this must be big enough to hold one converted floating-point value including +// padded zeros. +#ifndef PRINTF_DECIMAL_BUFFER_SIZE +#define PRINTF_DECIMAL_BUFFER_SIZE 32 +#endif + +// Support for the decimal notation floating point conversion specifiers (%f, %F) +#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS +#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 +#endif + +// Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) +#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 +#endif + +// Support for the length write-back specifier (%n) +#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER +#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 +#endif + +// Default precision for the floating point conversion specifiers (the C standard sets this at 6) +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6 +#endif + +// According to the C languages standard, printf() and related functions must be able to print any +// integral number in floating-point notation, regardless of length, when using the %f specifier - +// possibly hundreds of characters, potentially overflowing your buffers. In this implementation, +// all values beyond this threshold are switched to exponential notation. +#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL +#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 +#endif + +// Support for the long long integral types (with the ll, z and t length modifiers for specifiers +// %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long / *doubl e*/unsigned long) is not supported. +#ifndef PRINTF_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG 1 +#endif + +// The number of terms in a Taylor series expansion of log_10(x) to +// use for approximation - including the power-zero term (i.e. the +// value at the point of expansion). +#ifndef PRINTF_LOG10_TAYLOR_TERMS +#define PRINTF_LOG10_TAYLOR_TERMS 4 +#endif + +#if PRINTF_LOG10_TAYLOR_TERMS <= 1 +#error "At least one non-constant Taylor expansion is necessary for the log10() calculation" +#endif + +#define PRINTF_PREFER_DECIMAL false +#define PRINTF_PREFER_EXPONENTIAL true + +/////////////////////////////////////////////////////////////////////////////// + +// The following will convert the number-of-digits into an exponential-notation literal +#define PRINTF_CONCATENATE(s1, s2) s1##s2 +#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) +#define PRINTF_FLOAT_NOTATION_THRESHOLD PRINTF_EXPAND_THEN_CONCATENATE(1e, PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL) + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_INT (1U << 8U) +// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS +#define FLAGS_LONG (1U << 9U) +#define FLAGS_LONG_LONG (1U << 10U) +#define FLAGS_PRECISION (1U << 11U) +#define FLAGS_ADAPT_EXP (1U << 12U) +#define FLAGS_POINTER (1U << 13U) +// Note: Similar, but not identical, effect as FLAGS_HASH +#define FLAGS_SIGNED (1U << 14U) +// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +#define FLAGS_INT8 FLAGS_CHAR + +#if (SHRT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_SHORT +#elif (INT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_INT +#elif (LONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG +#elif (LLONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 16 bits exactly" +#endif + +#if (SHRT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_SHORT +#elif (INT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_INT +#elif (LONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG +#elif (LLONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 32 bits exactly" +#endif + +#if (SHRT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_SHORT +#elif (INT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_INT +#elif (LONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG +#elif (LLONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 64 bits exactly" +#endif + +#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +typedef unsigned int printf_flags_t; + +#define BASE_BINARY 2 +#define BASE_OCTAL 8 +#define BASE_DECIMAL 10 +#define BASE_HEX 16 + +typedef uint8_t numeric_base_t; + +#if PRINTF_SUPPORT_LONG_LONG +typedef unsigned long long printf_unsigned_value_t; +typedef long long printf_signed_value_t; +#else +typedef unsigned long printf_unsigned_value_t; +typedef long printf_signed_value_t; +#endif + +// The printf()-family functions return an `int`; it is therefore +// unnecessary/inappropriate to use size_t - often larger than int +// in practice - for non-negative related values, such as widths, +// precisions, offsets into buffers used for printing and the sizes +// of these buffers. instead, we use: +typedef unsigned int printf_size_t; +#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX +// If we were to nitpick, this would actually be INT_MAX + 1, +// since INT_MAX is the maximum return value, which excludes the +// trailing '\0'. + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) +#include +#if FLT_RADIX != 2 +#error "Non-binary-radix floating-point types are unsupported." +#endif + +#if DBL_MANT_DIG == 24 + +#define DOUBLE_SIZE_IN_BITS 32 +typedef uint32_t double_uint_t; +#define DOUBLE_EXPONENT_MASK 0xFFU +#define DOUBLE_BASE_EXPONENT 127 +#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -38 +#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-38 + +#elif DBL_MANT_DIG == 53 + +#define DOUBLE_SIZE_IN_BITS 64 +typedef uint64_t double_uint_t; +#define DOUBLE_EXPONENT_MASK 0x7FFU +#define DOUBLE_BASE_EXPONENT 1023 +#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -308 +#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-308 + +#else +#error "Unsupported / *doubl e*/unsigned long type configuration" +#endif +#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1) + +typedef union +{ + double_uint_t U; + /* double */ unsigned long F; +} double_with_bit_access; + +// This is unnecessary in C99, since compound initializers can be used, +// but: +// 1. Some compilers are finicky about this; +// 2. Some people may want to convert this to C89; +// 3. If you try to use it as C++, only C++20 supports compound literals +static inline double_with_bit_access get_bit_access(/* double */ unsigned long x) +{ + double_with_bit_access dwba; + dwba.F = x; + return dwba; +} + +static inline int get_sign_bit(/* double */ unsigned long x) +{ + // The sign is stored in the highest bit + return (int)(get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1)); +} + +static inline int get_exp2(double_with_bit_access x) +{ + // The exponent in an IEEE-754 floating-point number occupies a contiguous + // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An + // unsigned offset from some negative value (with the extremal offset values reserved for + // special use). + return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK) - DOUBLE_BASE_EXPONENT; +} +#define PRINTF_ABS(_x) ((_x) > 0 ? (_x) : -(_x)) + +#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid +// and well-defined, but if you're not careful you can easily trigger undefined +// behavior with -LONG_MIN or -LLONG_MIN +#define ABS_FOR_PRINTING(_x) ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x))) + +// wrapper (used as buffer) for output function type +// +// One of the following must hold: +// 1. max_chars is 0 +// 2. buffer is non-null +// 3. function is non-null +// +// ... otherwise bad things will happen. +typedef struct +{ + void (*function)(char c, void *extra_arg); + void *extra_function_arg; + char *buffer; + printf_size_t pos; + printf_size_t max_chars; +} output_gadget_t; + +// Note: This function currently assumes it is not passed a '\0' c, +// or alternatively, that '\0' can be passed to the function in the output +// gadget. The former assumption holds within the printf library. It also +// assumes that the output gadget has been properly initialized. +static inline void putchar_via_gadget(output_gadget_t *gadget, char c) +{ + printf_size_t write_pos = gadget->pos++; + // We're _always_ increasing pos, so as to count how may characters + // _would_ have been written if not for the max_chars limitation + if (write_pos >= gadget->max_chars) + { + return; + } + if (gadget->function != NULL) + { + // No check for c == '\0' . + gadget->function(c, gadget->extra_function_arg); + } + else + { + // it must be the case that gadget->buffer != NULL , due to the constraint + // on output_gadget_t ; and note we're relying on write_pos being non-negative. + gadget->buffer[write_pos] = c; + } +} + +// Possibly-write the string-terminating '\0' character +static inline void append_termination_with_gadget(output_gadget_t *gadget) +{ + if (gadget->function != NULL || gadget->max_chars == 0) + { + return; + } + if (gadget->buffer == NULL) + { + return; + } + printf_size_t null_char_pos = gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1; + gadget->buffer[null_char_pos] = '\0'; +} + +// We can't use putchar_ as is, since our output gadget +// only takes pointers to functions with an extra argument +static inline void putchar_wrapper(char c, void *unused) +{ + (void)unused; + putchar(c); +} + +static inline output_gadget_t discarding_gadget() +{ + output_gadget_t gadget; + gadget.function = NULL; + gadget.extra_function_arg = NULL; + gadget.buffer = NULL; + gadget.pos = 0; + gadget.max_chars = 0; + return gadget; +} + +static inline output_gadget_t buffer_gadget(char *buffer, size_t buffer_size) +{ + printf_size_t usable_buffer_size = (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) ? PRINTF_MAX_POSSIBLE_BUFFER_SIZE : (printf_size_t)buffer_size; + output_gadget_t result = discarding_gadget(); + if (buffer != NULL) + { + result.buffer = buffer; + result.max_chars = usable_buffer_size; + } + return result; +} + +static inline output_gadget_t function_gadget(void (*function)(char, void *), void *extra_arg) +{ + output_gadget_t result = discarding_gadget(); + result.function = function; + result.extra_function_arg = extra_arg; + result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE; + return result; +} + +static inline output_gadget_t extern_putchar_gadget() +{ + return function_gadget(putchar_wrapper, NULL); +} + +// internal secure strlen +// @return The length of the string (excluding the terminating 0) limited by 'maxsize' +// @note strlen uses size_t, but wes only use this function with printf_size_t +// variables - hence the signature. +static inline printf_size_t strnlen_s_(const char *str, printf_size_t maxsize) +{ + const char *s; + for (s = str; *s && maxsize--; ++s) + ; + return (printf_size_t)(s - str); +} + +// internal test if char is a digit (0-9) +// @return true if char is a digit +static inline bool is_digit_(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +// internal ASCII string to printf_size_t conversion +static printf_size_t atou_(const char **str) +{ + printf_size_t i = 0U; + while (is_digit_(**str)) + { + i = i * 10U + (printf_size_t)(*((*str)++) - '0'); + } + return i; +} + +// output the specified string in reverse, taking care of any zero-padding +static void out_rev_(output_gadget_t *output, const char *buf, printf_size_t len, printf_size_t width, printf_flags_t flags) +{ + const printf_size_t start_pos = output->pos; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) + { + for (printf_size_t i = len; i < width; i++) + { + putchar_via_gadget(output, ' '); + } + } + + // reverse string + while (len) + { + putchar_via_gadget(output, buf[--len]); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) + { + while (output->pos - start_pos < width) + { + putchar_via_gadget(output, ' '); + } + } +} + +// Invoked by print_integer after the actual number has been printed, performing necessary +// work on the number's prefix (as the number is initially printed in reverse order) +static void print_integer_finalization(output_gadget_t *output, char *buf, printf_size_t len, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags) +{ + printf_size_t unpadded_len = len; + + // pad with leading zeros + { + if (!(flags & FLAGS_LEFT)) + { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) + { + width--; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + { + buf[len++] = '0'; + } + } + + while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + { + buf[len++] = '0'; + } + + if (base == BASE_OCTAL && (len > unpadded_len)) + { + // Since we've written some zeros, we've satisfied the alternative format leading space requirement + flags &= ~FLAGS_HASH; + } + } + + // handle hash + if (flags & (FLAGS_HASH | FLAGS_POINTER)) + { + if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) + { + // Let's take back some padding digits to fit in what will eventually + // be the format-specific prefix + if (unpadded_len < len) + { + len--; // This should suffice for BASE_OCTAL + } + if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) + { + len--; // ... and an extra one for 0x or 0b + } + } + if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + { + buf[len++] = 'x'; + } + else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + { + buf[len++] = 'X'; + } + else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + { + buf[len++] = 'b'; + } + if (len < PRINTF_INTEGER_BUFFER_SIZE) + { + buf[len++] = '0'; + } + } + + if (len < PRINTF_INTEGER_BUFFER_SIZE) + { + if (negative) + { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) + { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) + { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +// An internal itoa-like function +static void print_integer(output_gadget_t *output, printf_unsigned_value_t value, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags) +{ + char buf[PRINTF_INTEGER_BUFFER_SIZE]; + printf_size_t len = 0U; + + if (!value) + { + if (!(flags & FLAGS_PRECISION)) + { + buf[len++] = '0'; + flags &= ~FLAGS_HASH; + // We drop this flag this since either the alternative and regular modes of the specifier + // don't differ on 0 values, or (in the case of octal) we've already provided the special + // handling for this mode. + } + else if (base == BASE_HEX) + { + flags &= ~FLAGS_HASH; + // We drop this flag this since either the alternative and regular modes of the specifier + // don't differ on 0 values + } + } + else + { + do + { + const char digit = (char)(value % base); + buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); + value /= base; + } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); + } + + print_integer_finalization(output, buf, len, negative, base, precision, width, flags); +} + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Stores a fixed-precision representation of a / *doubl e*/unsigned long relative +// to a fixed precision (which cannot be determined by examining this structure) +struct double_components +{ + int_fast64_t integral; + int_fast64_t fractional; + // ... truncation of the actual fractional part of the / *doubl e*/unsigned long value, scaled + // by the precision value + bool is_negative; +}; + +#define NUM_DECIMAL_DIGITS_IN_INT64_T 18 +#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T +static const /* double */ unsigned long powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = { + 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, + 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17}; + +#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1 + +// Break up a / *doubl e*/unsigned long number - which is known to be a finite non-negative number - +// into its base-10 parts: integral - before the decimal point, and fractional - after it. +// Taken the precision into account, but does not change it even internally. +static struct double_components get_components(/* double */ unsigned long number, printf_size_t precision) +{ + struct double_components number_; + number_.is_negative = get_sign_bit(number); + /* double */ unsigned long abs_number = (number_.is_negative) ? -number : number; + number_.integral = (int_fast64_t)abs_number; + /* double */ unsigned long remainder = (abs_number - (/* double */ unsigned long)number_.integral) * powers_of_10[precision]; + number_.fractional = (int_fast64_t)remainder; + + remainder -= (/* double */ unsigned long)number_.fractional; + + // if (remainder > 0.5) + warn("SSE not supported."); + if (remainder > 1) + { + ++number_.fractional; + // handle rollover, e.g. case 0.99 with precision 1 is 1.0 + if ((/* double */ unsigned long)number_.fractional >= powers_of_10[precision]) + { + number_.fractional = 0; + ++number_.integral; + } + } + // else if ((remainder == 0.5) && ((number_.fractional == 0U) || (number_.fractional & 1U))) + else if ((remainder == 1) && ((number_.fractional == 0U) || (number_.fractional & 1U))) + { + // if halfway, round up if odd OR if last digit is 0 + ++number_.fractional; + } + + if (precision == 0U) + { + remainder = abs_number - (/* double */ unsigned long)number_.integral; + // if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) + if ((!(remainder < 1) || (remainder > 1)) && (number_.integral & 1)) + { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++number_.integral; + } + } + return number_; +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +struct scaling_factor +{ + /* double */ unsigned long raw_factor; + bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it +}; + +static /* double */ unsigned long apply_scaling(/* double */ unsigned long num, struct scaling_factor normalization) +{ + return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; +} + +static /* double */ unsigned long unapply_scaling(/* double */ unsigned long normalized, struct scaling_factor normalization) +{ + return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor; +} + +static struct scaling_factor update_normalization(struct scaling_factor sf, /* double */ unsigned long extra_multiplicative_factor) +{ + struct scaling_factor result; + if (sf.multiply) + { + result.multiply = true; + result.raw_factor = sf.raw_factor * extra_multiplicative_factor; + } + else + { + int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); + int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); + + // Divide the larger-exponent raw raw_factor by the smaller + if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) + { + result.multiply = false; + result.raw_factor = sf.raw_factor / extra_multiplicative_factor; + } + else + { + result.multiply = true; + result.raw_factor = extra_multiplicative_factor / sf.raw_factor; + } + } + return result; +} + +static struct double_components get_normalized_components(bool negative, printf_size_t precision, /* double */ unsigned long non_normalized, struct scaling_factor normalization, int floored_exp10) +{ + struct double_components components; + components.is_negative = negative; + /* double */ unsigned long scaled = apply_scaling(non_normalized, normalization); + + bool close_to_representation_extremum = ((-floored_exp10 + (int)precision) >= DBL_MAX_10_EXP - 1); + if (close_to_representation_extremum) + { + // We can't have a normalization factor which also accounts for the precision, i.e. moves + // some decimal digits into the mantissa, since it's unrepresentable, or nearly unrepresentable. + // So, we'll give up early on getting extra precision... + return get_components(negative ? -scaled : scaled, precision); + } + components.integral = (int_fast64_t)scaled; + /* double */ unsigned long remainder = non_normalized - unapply_scaling((/* double */ unsigned long)components.integral, normalization); + /* double */ unsigned long prec_power_of_10 = powers_of_10[precision]; + struct scaling_factor account_for_precision = update_normalization(normalization, prec_power_of_10); + /* double */ unsigned long scaled_remainder = apply_scaling(remainder, account_for_precision); + /* double */ unsigned long rounding_threshold = 1; // 0.5; + + components.fractional = (int_fast64_t)scaled_remainder; // when precision == 0, the assigned value should be 0 + scaled_remainder -= (/* double */ unsigned long)components.fractional; // when precision == 0, this will not change scaled_remainder + + components.fractional += (scaled_remainder >= rounding_threshold); + if (scaled_remainder == rounding_threshold) + { + // banker's rounding: Round towards the even number (making the mean error 0) + components.fractional &= ~((int_fast64_t)0x1); + } + // handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), + // and must then be corrected into (1, 0). + // Note: for precision = 0, this will "translate" the rounding effect from + // the fractional part to the integral part where it should actually be + // felt (as prec_power_of_10 is 1) + if ((/* double */ unsigned long)components.fractional >= prec_power_of_10) + { + components.fractional = 0; + ++components.integral; + } + return components; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +static void print_broken_up_decimal( + struct double_components number_, output_gadget_t *output, printf_size_t precision, + printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len) +{ + if (precision != 0U) + { + // do fractional part, as an unsigned number + + printf_size_t count = precision; + + // %g/%G mandates we skip the trailing 0 digits... + if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (number_.fractional > 0)) + { + while (true) + { + int_fast64_t digit = number_.fractional % 10U; + if (digit != 0) + { + break; + } + --count; + number_.fractional /= 10U; + } + // ... and even the decimal point if there are no + // non-zero fractional part digits (see below) + } + + if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH)) + { + while (len < PRINTF_DECIMAL_BUFFER_SIZE) + { + --count; + buf[len++] = (char)('0' + number_.fractional % 10U); + if (!(number_.fractional /= 10U)) + { + break; + } + } + // add extra 0s + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U)) + { + buf[len++] = '0'; + --count; + } + if (len < PRINTF_DECIMAL_BUFFER_SIZE) + { + buf[len++] = '.'; + } + } + } + else + { + if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) + { + buf[len++] = '.'; + } + } + + // Write the integer part of the number (it comes after the fractional + // since the character order is reversed) + while (len < PRINTF_DECIMAL_BUFFER_SIZE) + { + buf[len++] = (char)('0' + (number_.integral % 10)); + if (!(number_.integral /= 10)) + { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) + { + if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) + { + width--; + } + while ((len < width) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) + { + buf[len++] = '0'; + } + } + + if (len < PRINTF_DECIMAL_BUFFER_SIZE) + { + if (number_.is_negative) + { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) + { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) + { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +// internal ftoa for fixed decimal floating point +static void print_decimal_number(output_gadget_t *output, /* double */ unsigned long number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len) +{ + struct double_components value_ = get_components(number, precision); + print_broken_up_decimal(value_, output, precision, width, flags, buf, len); +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +// A floor function - but one which only works for numbers whose +// floor value is representable by an int. +static int bastardized_floor(/* double */ unsigned long x) +{ + if (x >= 0) + { + return (int)x; + } + int n = (int)x; + return (((/* double */ unsigned long)n) == x) ? n : n - 1; +} + +// Computes the base-10 logarithm of the input number - which must be an actual +// positive number (not infinity or NaN, nor a sub-normal) +static /* double */ unsigned long log10_of_positive(/* double */ unsigned long positive_number) +{ + // The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c). + // + // Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of + // our input number, and need only solve log_10(M) for M between 1 and 2 (as + // the base-2 mantissa is always 1-point-something). In that limited range, a + // Taylor series expansion of log10(x) should serve us well enough; and we'll + // take the mid-point, 1.5, as the point of expansion. + + double_with_bit_access dwba = get_bit_access(positive_number); + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + int exp2 = get_exp2(dwba); + // drop the exponent, so dwba.F comes into the range [1,2) + dwba.U = (dwba.U & (((double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) | + ((double_uint_t)DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS); + /* double */ unsigned long z = (dwba.F - 1 /* 1.5 */); + // return ( + // // Taylor expansion around 1.5: + // 0.1760912590556812420 // Expansion term 0: ln(1.5) / ln(10) + // + z * 0.2895296546021678851 // Expansion term 1: (M - 1.5) * 2/3 / ln(10) + // #if PRINTF_LOG10_TAYLOR_TERMS > 2 + // - z * z * 0.0965098848673892950 // Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10) + // #if PRINTF_LOG10_TAYLOR_TERMS > 3 + // + z * z * z * 0.0428932821632841311 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10) + // #endif + // #endif + // // exact log_2 of the exponent x, with logarithm base change + // + exp2 * 0.30102999566398119521 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10) + // ); + return ( + // Taylor expansion around 1.5: + 0 // Expansion term 0: ln(1.5) / ln(10) + + z * 0 // Expansion term 1: (M - 1.5) * 2/3 / ln(10) +#if PRINTF_LOG10_TAYLOR_TERMS > 2 + - z * z * 0 // Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10) +#if PRINTF_LOG10_TAYLOR_TERMS > 3 + + z * z * z * 0 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10) +#endif +#endif + // exact log_2 of the exponent x, with logarithm base change + + exp2 * 0 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10) + ); +} + +static /* double */ unsigned long pow10_of_int(int floored_exp10) +{ + // A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values. + if (floored_exp10 == DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10) + { + return DOUBLE_MAX_SUBNORMAL_POWER_OF_10; + } + // Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow + double_with_bit_access dwba; + // int exp2 = bastardized_floor(floored_exp10 * 3.321928094887362 + 0.5); + // const /* double */ unsigned long z = floored_exp10 * 2.302585092994046 - exp2 * 0.6931471805599453; + int exp2 = bastardized_floor(floored_exp10 * 3 + 1 /* 0.5 */); + const /* double */ unsigned long z = floored_exp10 * 2 - exp2 * 0; + const /* double */ unsigned long z2 = z * z; + dwba.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS; + // compute exp(z) using continued fractions, + // see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + dwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + return dwba.F; +} + +static void print_exponential_number(output_gadget_t *output, /* double */ unsigned long number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len) +{ + const bool negative = get_sign_bit(number); + // This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it + /* double */ unsigned long abs_number = negative ? -number : number; + + int floored_exp10; + bool abs_exp10_covered_by_powers_table; + struct scaling_factor normalization; + + // Determine the decimal exponent + // if (abs_number == 0.0) + if (abs_number == 0) + { + // TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more generally. + floored_exp10 = 0; // ... and no need to set a normalization factor or check the powers table + } + else + { + /* double */ unsigned long exp10 = log10_of_positive(abs_number); + floored_exp10 = bastardized_floor(exp10); + /* double */ unsigned long p10 = pow10_of_int(floored_exp10); + // correct for rounding errors + if (abs_number < p10) + { + floored_exp10--; + p10 /= 10; + } + abs_exp10_covered_by_powers_table = PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; + normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10; + } + + // We now begin accounting for the widths of the two parts of our printed field: + // the decimal part after decimal exponent extraction, and the base-10 exponent part. + // For both of these, the value of 0 has a special meaning, but not the same one: + // a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width + // means "use as many characters as necessary". + + bool fall_back_to_decimal_only_mode = false; + if (flags & FLAGS_ADAPT_EXP) + { + int required_significant_digits = (precision == 0) ? 1 : (int)precision; + // Should we want to fall-back to "%f" mode, and only print the decimal part? + fall_back_to_decimal_only_mode = (floored_exp10 >= -4 && floored_exp10 < required_significant_digits); + // Now, let's adjust the precision + // This also decided how we adjust the precision value - as in "%g" mode, + // "precision" is the number of _significant digits_, and this is when we "translate" + // the precision value to an actual number of decimal digits. + int precision_ = fall_back_to_decimal_only_mode ? (int)precision - 1 - floored_exp10 : (int)precision - 1; // the presence of the exponent ensures only one significant digit comes before the decimal point + precision = (precision_ > 0 ? (unsigned)precision_ : 0U); + flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above + } + + normalization.multiply = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table); + bool should_skip_normalization = (fall_back_to_decimal_only_mode || floored_exp10 == 0); + struct double_components decimal_part_components = + should_skip_normalization ? get_components(negative ? -abs_number : abs_number, precision) : get_normalized_components(negative, precision, abs_number, normalization, floored_exp10); + + // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects + // the exponent and may require additional tweaking of the parts + if (fall_back_to_decimal_only_mode) + { + if ((flags & FLAGS_ADAPT_EXP) && floored_exp10 >= -1 && decimal_part_components.integral == powers_of_10[floored_exp10 + 1]) + { + floored_exp10++; // Not strictly necessary, since floored_exp10 is no longer really used + precision--; + // ... and it should already be the case that decimal_part_components.fractional == 0 + } + // TODO: What about rollover strictly within the fractional part? + } + else + { + if (decimal_part_components.integral >= 10) + { + floored_exp10++; + decimal_part_components.integral = 1; + decimal_part_components.fractional = 0; + } + } + + // the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit / *doubl e*/unsigned long + // is "307" (for 2^1023), so we set aside 4-5 characters overall + printf_size_t exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(floored_exp10) < 100) ? 4U + : 5U; + + printf_size_t decimal_part_width = + ((flags & FLAGS_LEFT) && exp10_part_width) ? + // We're padding on the right, so the width constraint is the exponent part's + // problem, not the decimal part's, so we'll use as many characters as we need: + 0U + : + // We're padding on the left; so the width constraint is the decimal part's + // problem. Well, can both the decimal part and the exponent part fit within our overall width? + ((width > exp10_part_width) ? + // Yes, so we limit our decimal part's width. + // (Note this is trivially valid even if we've fallen back to "%f" mode) + width - exp10_part_width + : + // No; we just give up on any restriction on the decimal part and use as many + // characters as we need + 0U); + + const printf_size_t printed_exponential_start_pos = output->pos; + print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, buf, len); + + if (!fall_back_to_decimal_only_mode) + { + putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e'); + print_integer(output, + ABS_FOR_PRINTING(floored_exp10), + floored_exp10 < 0, 10, 0, exp10_part_width - 1, + FLAGS_ZEROPAD | FLAGS_PLUS); + if (flags & FLAGS_LEFT) + { + // We need to right-pad with spaces to meet the width requirement + while (output->pos - printed_exponential_start_pos < width) + { + putchar_via_gadget(output, ' '); + } + } + } +} +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +static void print_floating_point(output_gadget_t *output, /* double */ unsigned long value, printf_size_t precision, printf_size_t width, printf_flags_t flags, bool prefer_exponential) +{ + char buf[PRINTF_DECIMAL_BUFFER_SIZE]; + printf_size_t len = 0U; + + // test for special values + if (value != value) + { + out_rev_(output, "nan", 3, width, flags); + return; + } + // if (value < -DBL_MAX) + if (value < -1) + { + out_rev_(output, "fni-", 4, width, flags); + return; + } + // if (value > DBL_MAX) + if (value > 1) + { + out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + return; + } + + if (!prefer_exponential && + // ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) + ((value > 1) || (value < -1))) + { + // The required behavior of standard printf is to print _every_ integral-part digit -- which could mean + // printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated + // implementation. +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + print_exponential_number(output, value, precision, width, flags, buf, len); +#endif + return; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) + { + precision = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // limit precision so that our integer holding the fractional part does not overflow + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) + { + buf[len++] = '0'; // This respects the precision in terms of result length only + precision--; + } + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + if (prefer_exponential) + print_exponential_number(output, value, precision, width, flags, buf, len); + else +#endif + print_decimal_number(output, value, precision, width, flags, buf, len); +} + +#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Advances the format pointer past the flags, and returns the parsed flags +// due to the characters passed +static printf_flags_t parse_flags(const char **format) +{ + printf_flags_t flags = 0U; + do + { + switch (**format) + { + case '0': + flags |= FLAGS_ZEROPAD; + (*format)++; + break; + case '-': + flags |= FLAGS_LEFT; + (*format)++; + break; + case '+': + flags |= FLAGS_PLUS; + (*format)++; + break; + case ' ': + flags |= FLAGS_SPACE; + (*format)++; + break; + case '#': + flags |= FLAGS_HASH; + (*format)++; + break; + default: + return flags; + } + } while (true); +} + +// internal vsnprintf - used for implementing _all library functions +// Note: We don't like the C standard's parameter names, so using more informative parameter names +// here instead. +static int _vsnprintf(output_gadget_t *output, const char *format, va_list args) +{ + // Note: The library only calls _vsnprintf() with output->pos being 0. However, it is + // possible to call this function with a non-zero pos value for some "remedial printing". + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') + { + // no + putchar_via_gadget(output, *format); + format++; + continue; + } + else + { + // yes, evaluate it + format++; + } + + printf_flags_t flags = parse_flags(&format); + + // evaluate width field + printf_size_t width = 0U; + if (is_digit_(*format)) + { + width = (printf_size_t)atou_(&format); + } + else if (*format == '*') + { + const int w = va_arg(args, int); + if (w < 0) + { + flags |= FLAGS_LEFT; // reverse padding + width = (printf_size_t)-w; + } + else + { + width = (printf_size_t)w; + } + format++; + } + + // evaluate precision field + printf_size_t precision = 0U; + if (*format == '.') + { + flags |= FLAGS_PRECISION; + format++; + if (is_digit_(*format)) + { + precision = (printf_size_t)atou_(&format); + } + else if (*format == '*') + { + const int precision_ = va_arg(args, int); + precision = precision_ > 0 ? (printf_size_t)precision_ : 0U; + format++; + } + } + + // evaluate length field + switch (*format) + { +#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + case 'I': + { + format++; + // Greedily parse for size in bits: 8, 16, 32 or 64 + switch (*format) + { + case '8': + flags |= FLAGS_INT8; + format++; + break; + case '1': + format++; + if (*format == '6') + { + format++; + flags |= FLAGS_INT16; + } + break; + case '3': + format++; + if (*format == '2') + { + format++; + flags |= FLAGS_INT32; + } + break; + case '6': + format++; + if (*format == '4') + { + format++; + flags |= FLAGS_INT64; + } + break; + default: + break; + } + break; + } +#endif + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') + { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') + { + flags |= FLAGS_CHAR; + format++; + } + break; + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch (*format) + { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': + { + + if (*format == 'd' || *format == 'i') + { + flags |= FLAGS_SIGNED; + } + + numeric_base_t base; + if (*format == 'x' || *format == 'X') + { + base = BASE_HEX; + } + else if (*format == 'o') + { + base = BASE_OCTAL; + } + else if (*format == 'b') + { + base = BASE_BINARY; + } + else + { + base = BASE_DECIMAL; + flags &= ~FLAGS_HASH; // decimal integers have no alternative presentation + } + + if (*format == 'X') + { + flags |= FLAGS_UPPERCASE; + } + + format++; + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) + { + flags &= ~FLAGS_ZEROPAD; + } + + if (flags & FLAGS_SIGNED) + { + // A signed specifier: d, i or possibly I + bit size if enabled + + if (flags & FLAGS_LONG_LONG) + { +#if PRINTF_SUPPORT_LONG_LONG + const long long value = va_arg(args, long long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) + { + const long value = va_arg(args, long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + } + else + { + // We never try to interpret the argument as something potentially-smaller than int, + // due to integer promotion rules: Even if the user passed a short int, short unsigned + // etc. - these will come in after promotion, as int's (or unsigned for the case of + // short unsigned when it has the same size as int) + const int value = + (flags & FLAGS_CHAR) ? (signed char)va_arg(args, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int) + : va_arg(args, int); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + } + } + else + { + // An unsigned specifier: u, x, X, o, b + + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + + if (flags & FLAGS_LONG_LONG) + { +#if PRINTF_SUPPORT_LONG_LONG + print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) + { + print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long), false, base, precision, width, flags); + } + else + { + const unsigned int value = + (flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(args, unsigned int) + : va_arg(args, unsigned int); + print_integer(output, (printf_unsigned_value_t)value, false, base, precision, width, flags); + } + } + break; + } +#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + print_floating_point(output, va_arg(args, /* double */ unsigned long), precision, width, flags, PRINTF_PREFER_DECIMAL); + format++; + break; +#endif +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + print_floating_point(output, va_arg(args, /* double */ unsigned long), precision, width, flags, PRINTF_PREFER_EXPONENTIAL); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + case 'c': + { + printf_size_t l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) + { + while (l++ < width) + { + putchar_via_gadget(output, ' '); + } + } + // char output + putchar_via_gadget(output, (char)va_arg(args, int)); + // post padding + if (flags & FLAGS_LEFT) + { + while (l++ < width) + { + putchar_via_gadget(output, ' '); + } + } + format++; + break; + } + + case 's': + { + const char *p = va_arg(args, char *); + if (p == NULL) + { + out_rev_(output, ")llun(", 6, width, flags); + } + else + { + printf_size_t l = strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE); + // pre padding + if (flags & FLAGS_PRECISION) + { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) + { + while (l++ < width) + { + putchar_via_gadget(output, ' '); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision)) + { + putchar_via_gadget(output, *(p++)); + --precision; + } + // post padding + if (flags & FLAGS_LEFT) + { + while (l++ < width) + { + putchar_via_gadget(output, ' '); + } + } + } + format++; + break; + } + + case 'p': + { + width = sizeof(void *) * 2U + 2; // 2 hex chars per byte + the "0x" prefix + flags |= FLAGS_ZEROPAD | FLAGS_POINTER; + uintptr_t value = (uintptr_t)va_arg(args, void *); + (value == (uintptr_t)NULL) ? out_rev_(output, ")lin(", 5, width, flags) : print_integer(output, (printf_unsigned_value_t)value, false, BASE_HEX, precision, width, flags); + format++; + break; + } + + case '%': + putchar_via_gadget(output, '%'); + format++; + break; + + // Many people prefer to disable support for %n, as it lets the caller + // engineer a write to an arbitrary location, of a value the caller + // effectively controls - which could be a security concern in some cases. +#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER + case 'n': + { + if (flags & FLAGS_CHAR) + *(va_arg(args, char *)) = (char)output->pos; + else if (flags & FLAGS_SHORT) + *(va_arg(args, short *)) = (short)output->pos; + else if (flags & FLAGS_LONG) + *(va_arg(args, long *)) = (long)output->pos; +#if PRINTF_SUPPORT_LONG_LONG + else if (flags & FLAGS_LONG_LONG) + *(va_arg(args, long long *)) = (long long int)output->pos; +#endif // PRINTF_SUPPORT_LONG_LONG + else + *(va_arg(args, int *)) = (int)output->pos; + format++; + break; + } +#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER + + default: + putchar_via_gadget(output, *format); + format++; + break; + } + } + + // termination + append_termination_with_gadget(output); + + // return written chars without terminating \0 + return (int)output->pos; +} + +/////////////////////////////////////////////////////////////////////////////// + +int vprintf_(const char *format, va_list arg) +{ + output_gadget_t gadget = extern_putchar_gadget(); + return _vsnprintf(&gadget, format, arg); +} + +int vsnprintf_(char *s, size_t n, const char *format, va_list arg) +{ + output_gadget_t gadget = buffer_gadget(s, n); + return _vsnprintf(&gadget, format, arg); +} + +int vsprintf_(char *s, const char *format, va_list arg) +{ + return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg); +} + +int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, va_list arg) +{ + output_gadget_t gadget = function_gadget(out, extra_arg); + return _vsnprintf(&gadget, format, arg); +} + +int printf_(const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vprintf_(format, args); + va_end(args); + return ret; +} + +int sprintf_(char *s, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vsprintf_(s, format, args); + va_end(args); + return ret; +} + +int snprintf_(char *s, size_t n, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vsnprintf_(s, n, format, args); + va_end(args); + return ret; +} + +int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vfctprintf(out, extra_arg, format, args); + va_end(args); + return ret; +} + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/UEFI/src/printf.h b/UEFI/src/printf.h new file mode 100644 index 0000000..9191a36 --- /dev/null +++ b/UEFI/src/printf.h @@ -0,0 +1,195 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2022, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with + * a very limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PRINTF_H_ +#define PRINTF_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __GNUC__ +#define ATTR_PRINTF(one_based_format_index, first_arg) \ + __attribute__((format(__printf__, (one_based_format_index), (first_arg)))) +#define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF((one_based_format_index), 0) +#else +#define ATTR_PRINTF((one_based_format_index), (first_arg)) +#define ATTR_VPRINTF(one_based_format_index) +#endif + +#ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0 +#endif + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#define printf_ printf +#define sprintf_ sprintf +#define vsprintf_ vsprintf +#define snprintf_ snprintf +#define vsnprintf_ vsnprintf +#define vprintf_ vprintf +#endif + +// If you want to include this implementation file directly rather than +// link against, this will let you control the functions' visibility, +// e.g. make them static so as not to clash with other objects also +// using them. +#ifndef PRINTF_VISIBILITY +#define PRINTF_VISIBILITY +#endif + + /** + * Prints/send a single character to some opaque output entity + * + * @note This function is not implemented by the library, only declared; you must provide an + * implementation if you wish to use the @ref printf / @ref vprintf function (and possibly + * for linking against the library, if your toolchain does not support discarding unused functions) + * + * @note The output could be as simple as a wrapper for the `write()` system call on a Unix-like + * system, or even libc's @ref putchar , for replicating actual functionality of libc's @ref printf + * function; but on an embedded system it may involve interaction with a special output device, + * like a UART, etc. + * + * @note in libc's @ref putchar, the parameter type is an int; this was intended to support the + * representation of either a proper character or EOF in a variable - but this is really not + * meaningful to pass into @ref putchar and is discouraged today. See further discussion in: + * @link https://stackoverflow.com/q/17452847/1593077 + * + * @param c the single character to print + */ + PRINTF_VISIBILITY + void putchar(char c); + + /** + * An implementation of the C standard's printf/vprintf + * + * @note you must implement a @ref putchar_ function for using this function - it invokes @ref putchar_ + * rather than directly performing any I/O (which insulates it from any dependence on the operating system + * and external libraries). + * + * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret + * additional arguments. + * @param arg Additional arguments to the function, one for each %-specifier in @p format string + * @return The number of characters written into @p s, not counting the terminating null character + */ + ///@{ + PRINTF_VISIBILITY + int printf_(const char *format, ...) ATTR_PRINTF(1, 2); + PRINTF_VISIBILITY + int vprintf_(const char *format, va_list arg) ATTR_VPRINTF(1); + ///@} + + /** + * An implementation of the C standard's sprintf/vsprintf + * + * @note For security considerations (the potential for exceeding the buffer bounds), please consider using + * the size-constrained variant, @ref snprintf / @ref vsnprintf , instead. + * + * @param s An array in which to store the formatted string. It must be large enough to fit the formatted + * output! + * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret + * additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters written into @p s, not counting the terminating null character + */ + ///@{ + PRINTF_VISIBILITY + int sprintf_(char *s, const char *format, ...) ATTR_PRINTF(2, 3); + PRINTF_VISIBILITY + int vsprintf_(char *s, const char *format, va_list arg) ATTR_VPRINTF(2); + ///@} + + /** + * An implementation of the C standard's snprintf/vsnprintf + * + * @param s An array in which to store the formatted string. It must be large enough to fit either the + * entire formatted output, or at least @p n characters. Alternatively, it can be NULL, in which case + * nothing will be printed, and only the number of characters which _could_ have been printed is + * tallied and returned. + * @param n The maximum number of characters to write to the array, including a terminating null character + * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret + * additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters that COULD have been written into @p s, not counting the terminating + * null character. A value equal or larger than @p n indicates truncation. Only when the returned value + * is non-negative and less than @p n, the null-terminated string has been fully and successfully printed. + */ + ///@{ + PRINTF_VISIBILITY + int snprintf_(char *s, size_t count, const char *format, ...) ATTR_PRINTF(3, 4); + PRINTF_VISIBILITY + int vsnprintf_(char *s, size_t count, const char *format, va_list arg) ATTR_VPRINTF(3); + ///@} + + /** + * printf/vprintf with user-specified output function + * + * An alternative to @ref printf_, in which the output function is specified dynamically + * (rather than @ref putchar_ being used) + * + * @param out An output function which takes one character and a type-erased additional parameters + * @param extra_arg The type-erased argument to pass to the output function @p out with each call + * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret + * additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters for which the output f unction was invoked, not counting the terminating null character + * + */ + PRINTF_VISIBILITY + int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) ATTR_PRINTF(3, 4); + PRINTF_VISIBILITY + int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, va_list arg) ATTR_VPRINTF(3); + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#undef printf_ +#undef sprintf_ +#undef vsprintf_ +#undef snprintf_ +#undef vsnprintf_ +#undef vprintf_ +#endif + +#ifdef __cplusplus +} +#endif + +#endif // PRINTF_H_ \ No newline at end of file diff --git a/UEFI/types.h b/UEFI/types.h new file mode 100644 index 0000000..255ab69 --- /dev/null +++ b/UEFI/types.h @@ -0,0 +1,17 @@ +#ifndef LYNX_TYPES_H +#define LYNX_TYPES_H + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __SIZE_TYPE__ size_t; +typedef __UINTPTR_TYPE__ uintptr_t; +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define ALIGN_UP(x, align) ((__typeof__(x))(((uint64_t)(x) + ((align)-1)) & (~((align)-1)))) +#define ALIGN_DOWN(x, align) ((__typeof__(x))((x) & (~((align)-1)))) + +#endif // !LYNX_TYPES_H