├── docs ├── USAGE.md ├── CONFIG.md ├── sonar.png └── ECHO.md ├── src ├── lib │ ├── log.c │ ├── string.h │ ├── log.h │ ├── bitmap.h │ ├── string.c │ ├── bitmap.c │ ├── io.h │ └── io.c ├── boot │ └── limine.cfg ├── drivers │ ├── serial.h │ └── serial.c ├── sys │ ├── ports.h │ └── ports.c ├── Makefile ├── main.c ├── linker.ld └── protos │ ├── echo.h │ └── limine.h ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── Makefile └── README.md /docs/USAGE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/log.c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CONFIG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | 4 | build/* 5 | *.o -------------------------------------------------------------------------------- /docs/sonar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shreyaslad/sonar/HEAD/docs/sonar.png -------------------------------------------------------------------------------- /src/boot/limine.cfg: -------------------------------------------------------------------------------- 1 | TIMEOUT=0 2 | 3 | :SONAR 4 | PROTOCOL=limine 5 | 6 | KERNEL_PATH=boot:///sonar.elf -------------------------------------------------------------------------------- /src/drivers/serial.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIAL_H 2 | #define SERIAL_H 3 | 4 | #include 5 | #include 6 | 7 | void serial_write(char c); 8 | 9 | void init_serial(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amd64/ubuntu:latest 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y \ 5 | clang \ 6 | lld \ 7 | nasm \ 8 | gcc \ 9 | g++ \ 10 | make \ 11 | git \ 12 | xorriso 13 | 14 | ADD . /sonar 15 | WORKDIR /sonar 16 | 17 | ENV DOCKER_BUILD=1 18 | 19 | CMD ["make", "sonar"] 20 | -------------------------------------------------------------------------------- /src/sys/ports.h: -------------------------------------------------------------------------------- 1 | #ifndef PORTS_H 2 | #define PORTS_H 3 | 4 | #include 5 | 6 | uint8_t inb(uint16_t port); 7 | void outb(uint16_t port, uint8_t data); 8 | 9 | uint16_t inw(uint16_t port); 10 | void outw(uint16_t port, uint16_t data); 11 | 12 | uint32_t ind(uint16_t port); 13 | void outd(uint16_t port, uint32_t data); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/lib/string.h: -------------------------------------------------------------------------------- 1 | #ifndef STRING_H 2 | #define STRING_H 3 | 4 | #include 5 | #include 6 | 7 | void* memset(void* ptr, int value, size_t num); 8 | int memcmp(const void* ptr1, const void* ptr2, size_t num); 9 | void* memcpy(void* dest, const void* src, size_t num); 10 | void* memmove(void* dest, const void* src, size_t num); 11 | 12 | #endif -------------------------------------------------------------------------------- /src/lib/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | 6 | #define PRN(stat, fmt, ...) \ 7 | printf(stat " %-6s: " fmt, __MODULE__, ## __VA_ARGS__) 8 | 9 | #define LOG(fmt, ...) PRN("[*]", fmt, ## __VA_ARGS__) 10 | #define WARN(fmt, ...) PRN("[-]", fmt, ## __VA_ARGS__) 11 | #define ERR(fmt, ...) PRN("[!]", fmt, ## __VA_ARGS__) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/lib/bitmap.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAP_H 2 | #define BITMAP_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | struct bitmap { 11 | uint64_t* bitmap; 12 | size_t len; 13 | }; 14 | 15 | uint64_t find_free_block(struct bitmap* bitmap, size_t n); 16 | 17 | void bitmap_alloc(struct bitmap* bitmap, size_t abs_start, size_t n); 18 | void bitmap_free(struct bitmap* bitmap, uint64_t abs_pos, size_t n); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/drivers/serial.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PORT 0x3F8 /* COM1 */ 4 | 5 | void init_serial() { 6 | outb(PORT + 1, 0x00); // Disable all interrupts 7 | outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) 8 | outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud 9 | outb(PORT + 1, 0x00); // (hi byte) 10 | outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit 11 | outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold 12 | outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set 13 | } 14 | 15 | static int is_transmit_empty() { 16 | return inb(PORT + 5) & 0x20; 17 | } 18 | 19 | void serial_write(char c) { 20 | while (!is_transmit_empty()); 21 | 22 | outb(PORT, c); 23 | } 24 | -------------------------------------------------------------------------------- /src/sys/ports.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint8_t inb(uint16_t port) { 4 | uint8_t ret; 5 | __asm__("inb %%dx, %%al" : "=a"(ret) : "d"(port)); 6 | return ret; 7 | } 8 | 9 | void outb(uint16_t port, uint8_t data) { 10 | __asm__("outb %%al, %%dx" : : "a"(data), "d"(port)); 11 | } 12 | 13 | uint16_t inw(uint16_t port) { 14 | uint16_t ret; 15 | __asm__("inw %%dx, %%ax" : "=a"(ret) : "d"(port)); 16 | return ret; 17 | } 18 | 19 | void outw(uint16_t port, uint16_t data) { 20 | __asm__("outw %%ax, %%dx" : : "a"(data), "d"(port)); 21 | } 22 | 23 | uint32_t ind(uint16_t port) { 24 | uint32_t ret; 25 | __asm__("inl %%dx, %%eax" : "=a"(ret) : "d"(port)); 26 | return ret; 27 | } 28 | 29 | void outd(uint16_t port, uint32_t data) { 30 | __asm__("outl %%eax, %%dx" : : "a"(data), "d"(port)); 31 | } 32 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | default: all 2 | .PHONY: all 3 | 4 | CFLAGS = \ 5 | -target ${ARCH}-unknown-none \ 6 | -std=gnu99 \ 7 | -ffreestanding \ 8 | -Wall \ 9 | -Wextra \ 10 | -fno-stack-protector \ 11 | -fno-stack-check \ 12 | -fno-lto \ 13 | -fno-pie \ 14 | -fno-pic \ 15 | -m64 \ 16 | -mno-mmx \ 17 | -mno-sse \ 18 | -mno-sse2 \ 19 | -mno-red-zone \ 20 | -mcmodel=kernel \ 21 | -I. 22 | 23 | LDFLAGS = \ 24 | -O2 \ 25 | -ffreestanding \ 26 | -nostdlib \ 27 | -no-pie \ 28 | -z max-page-size=0x1000 \ 29 | -T linker.ld 30 | 31 | ASMFLAGS = \ 32 | -f elf64 \ 33 | -f dwarf \ 34 | -g 35 | 36 | C_SOURCES = $(shell find . -type f -name '*.c') 37 | H_SOURCES = $(shell find . -type f -name '*.h') 38 | ASM_SOURCES = $(shell find . -type f -name '*.asm') 39 | 40 | OBJ = ${C_SOURCES:.c=.o} ${ASM_SOURCES:.asm=.o} 41 | 42 | all: ${SONAR_KERNEL_TARGET} 43 | 44 | ${SONAR_KERNEL_TARGET}: ${OBJ} 45 | ${LD} ${LDFLAGS} ${OBJ} -o $@ 46 | 47 | %.o: %.c 48 | ${CC} ${CFLAGS} -c $< -o $@ 49 | 50 | %.o: %.asm 51 | ${AS} ${ASMFLAGS} -o $@ $< 52 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #undef __MODULE__ 11 | #define __MODULE__ "kernel" 12 | 13 | static volatile struct limine_memmap_request mmap_request = { 14 | .id = LIMINE_MEMMAP_REQUEST, 15 | .revision = 0 16 | }; 17 | 18 | static volatile struct limine_hhdm_request hhdm_request = { 19 | .id = LIMINE_HHDM_REQUEST, 20 | .revision = 0 21 | }; 22 | 23 | static volatile struct limine_framebuffer_request fb_request = { 24 | .id = LIMINE_FRAMEBUFFER_REQUEST, 25 | .revision = 0 26 | }; 27 | 28 | static volatile struct limine_terminal_request terminal_request = { 29 | .id = LIMINE_TERMINAL_REQUEST, 30 | .revision = 0 31 | }; 32 | 33 | __attribute__((noreturn)) 34 | void _start(void) { 35 | init_serial(); 36 | 37 | LOG("Initialized early logging\n"); 38 | 39 | init_mem(mmap_request.response, hhdm_request.response); 40 | 41 | for (;;) { 42 | __asm__("hlt"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Shreyas Lad 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/lib/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define BYTES_TO_MIB(b) b / 1048576 4 | 5 | void* memset(void* ptr, int value, size_t num) { 6 | uint8_t* buf = (uint8_t *)ptr; 7 | 8 | for (size_t i = 0; i < num; i++) 9 | buf[i] = (uint8_t)value; 10 | 11 | return ptr; 12 | } 13 | 14 | int memcmp(const void* ptr1, const void* ptr2, size_t num) { 15 | const uint8_t* a = (const uint8_t *)ptr1; 16 | const uint8_t* b = (const uint8_t *)ptr2; 17 | 18 | for (size_t i = 0; i < num; i++) { 19 | if(a[i] < b[i]) 20 | return -1; 21 | else if(b[i] < a[i]) 22 | return 1; 23 | } 24 | 25 | return 0; 26 | } 27 | 28 | void* memcpy(void* dest, const void* src, size_t n) { 29 | uint8_t* _dst = (uint8_t *)dest; 30 | const uint8_t* _src = (const uint8_t *)src; 31 | 32 | for(size_t i = 0; i < n; i++) 33 | _dst[i] = _src[i]; 34 | 35 | return dest; 36 | } 37 | 38 | void* memmove(void* dest, const void* src, size_t num) { 39 | uint8_t* pdest = (uint8_t *)dest; 40 | const uint8_t* psrc = (const uint8_t *)src; 41 | 42 | if (src > dest) { 43 | for (size_t i = 0; i < num; i++) { 44 | pdest[i] = psrc[i]; 45 | } 46 | } else if (src < dest) { 47 | for (size_t i = num; i > 0; i--) { 48 | pdest[i - 1] = psrc[i - 1]; 49 | } 50 | } 51 | 52 | return dest; 53 | } -------------------------------------------------------------------------------- /src/linker.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want an x86_64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-x86-64) 3 | OUTPUT_ARCH(i386:x86-64) 4 | 5 | /* We want the symbol _start to be our entry point */ 6 | ENTRY(_start) 7 | 8 | /* Define the program headers we want so the bootloader gives us the right */ 9 | /* MMU permissions */ 10 | PHDRS 11 | { 12 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 13 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 14 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 15 | } 16 | 17 | SECTIONS 18 | { 19 | /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */ 20 | /* and because that is what the Limine spec mandates. */ 21 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 22 | /* that is the beginning of the region. */ 23 | . = 0xffffffff80000000; 24 | 25 | .text : { 26 | *(.text .text.*) 27 | } :text 28 | 29 | /* Move to the next memory page for .rodata */ 30 | . += CONSTANT(MAXPAGESIZE); 31 | 32 | .rodata : { 33 | *(.rodata .rodata.*) 34 | } :rodata 35 | 36 | /* Move to the next memory page for .data */ 37 | . += CONSTANT(MAXPAGESIZE); 38 | 39 | .data : { 40 | *(.data .data.*) 41 | } :data 42 | 43 | .bss : { 44 | *(COMMON) 45 | *(.bss .bss.*) 46 | } :data 47 | 48 | /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ 49 | /DISCARD/ : { 50 | *(.eh_frame) 51 | *(.note .note.*) 52 | } 53 | } -------------------------------------------------------------------------------- /src/lib/bitmap.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Helper Functions */ 4 | 5 | static uint64_t get_bitmap_entry(struct bitmap* bitmap, uint64_t abs_pos) { 6 | return bitmap->bitmap[abs_pos / PAGE_SIZE]; 7 | } 8 | 9 | static uint8_t get_abs_bit(struct bitmap* bitmap, uint64_t abs_pos) { 10 | uint64_t entry = get_bitmap_entry(bitmap, abs_pos); 11 | uint64_t mask = (1 << (abs_pos % 64)); 12 | 13 | return (bitmap->bitmap[entry] & mask) == mask; 14 | } 15 | 16 | static void set_abs_bit(struct bitmap* bitmap, uint64_t abs_pos) { 17 | uint64_t entry = get_bitmap_entry(bitmap, abs_pos); 18 | 19 | bitmap->bitmap[entry] |= (1 << (abs_pos % 64)); 20 | } 21 | 22 | static void clear_abs_bit(struct bitmap* bitmap, uint64_t abs_pos) { 23 | uint64_t entry = get_bitmap_entry(bitmap, abs_pos); 24 | 25 | bitmap->bitmap[entry] &= ~(1 << (abs_pos % 64)); 26 | } 27 | 28 | /* Allocation Functions */ 29 | 30 | uint64_t find_free_block(struct bitmap* bitmap, size_t n) { 31 | uint64_t start_pos = 0; 32 | bool block_found = false; 33 | 34 | uint64_t cur_bits_found = 0; 35 | 36 | // Loop through every possible page instead of 37 | // dealing with bitmap entries and offsets 38 | for (uint64_t i = 0; i < bitmap->len * 64; i++) { 39 | 40 | // The efficiency of doing this might be outweighed 41 | // by the inefficiency of running this calculation 42 | // on every bit 43 | // uint64_t entry = get_bitmap_entry(bitmap, i); 44 | 45 | // if (entry == 0xff) { 46 | // i += 64; 47 | // continue; 48 | // } 49 | 50 | if (!get_abs_bit(bitmap, i)) { 51 | cur_bits_found = 0; 52 | continue; 53 | } 54 | 55 | cur_bits_found++; 56 | 57 | if (cur_bits_found == n) { 58 | block_found = true; 59 | start_pos = i; 60 | 61 | break; 62 | } 63 | } 64 | 65 | // TODO: Panic if no free blocks found 66 | if (!block_found) 67 | ;; 68 | 69 | return start_pos; 70 | } 71 | 72 | void bitmap_alloc(struct bitmap* bitmap, size_t abs_start, size_t n) { 73 | for (size_t i = abs_start; i < abs_start + n; i++) { 74 | set_abs_bit(bitmap, i); 75 | } 76 | } 77 | 78 | 79 | void bitmap_free(struct bitmap* bitmap, uint64_t abs_pos, size_t n) { 80 | for (size_t i = 0; i < n; i++) { 81 | clear_abs_bit(bitmap, abs_pos + i); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: all 2 | 3 | .PHONY: \ 4 | all \ 5 | build \ 6 | run \ 7 | debug \ 8 | sonar \ 9 | kernel \ 10 | clean \ 11 | limine 12 | 13 | ########### 14 | # Tooling # 15 | ########### 16 | 17 | export \ 18 | ARCH \ 19 | CC \ 20 | LD \ 21 | AS 22 | 23 | ARCH = x86_64 24 | 25 | CC = clang 26 | LD = gcc 27 | AS = nasm 28 | 29 | QFLAGS = \ 30 | -name sonar \ 31 | -m 2G \ 32 | -boot menu=off \ 33 | -smp cpus=4 \ 34 | -cdrom ${SONAR_ISO_TARGET} \ 35 | -device ahci,id=ahci \ 36 | -drive if=none,id=disk,file=${SONAR_ISO_TARGET},format=raw \ 37 | -machine q35 38 | 39 | ######################### 40 | # Top Level Directories # 41 | ######################### 42 | 43 | export \ 44 | BOOT_DIR \ 45 | SRC_DIR \ 46 | BUILD_DIR 47 | 48 | DIR = $(shell readlink -f .) 49 | 50 | SRC_DIR = ${DIR}/src 51 | BOOT_DIR = ${SRC_DIR}/boot 52 | BUILD_DIR = ${DIR}/build 53 | 54 | ################ 55 | # Build Output # 56 | ################ 57 | 58 | export \ 59 | SONAR_KERNEL_TARGET \ 60 | SONAR_IMG_TARGET 61 | 62 | SONAR_KERNEL_TARGET = ${BUILD_DIR}/sonar.elf 63 | SONAR_MOUNT_TARGET = ${BUILD_DIR}/iso_root 64 | SONAR_ISO_TARGET = ${BUILD_DIR}/sonar.iso 65 | 66 | ################ 67 | # Dependencies # 68 | ################ 69 | 70 | LIMINE_DIR = ${BUILD_DIR}/limine 71 | LIMINE_REPO = https://github.com/limine-bootloader/limine 72 | LIMINE_BRANCH = v4.x-branch-binary 73 | 74 | 75 | all: build run 76 | 77 | build: 78 | docker run \ 79 | --rm \ 80 | -v ${BUILD_DIR}:/sonar/build \ 81 | $(shell docker build -q . || echo "Build failed") 82 | 83 | run: 84 | qemu-system-${ARCH} ${QFLAGS} -serial stdio 85 | 86 | debug: 87 | qemu-system-${ARCH} ${QFLAGS} -no-reboot -d int 88 | 89 | ################# 90 | # Raw ISO Build # 91 | ################# 92 | 93 | sonar: kernel limine 94 | mkdir -p ${SONAR_MOUNT_TARGET} 95 | 96 | cp -v \ 97 | ${SONAR_KERNEL_TARGET} \ 98 | ${BOOT_DIR}/limine.cfg \ 99 | ${LIMINE_DIR}/limine.sys \ 100 | ${LIMINE_DIR}/limine-cd.bin \ 101 | ${LIMINE_DIR}/limine-cd-efi.bin \ 102 | ${SONAR_MOUNT_TARGET} 103 | 104 | xorriso -as mkisofs \ 105 | -b limine-cd.bin \ 106 | -no-emul-boot \ 107 | -boot-load-size 4 \ 108 | -boot-info-table \ 109 | --efi-boot limine-cd-efi.bin \ 110 | -efi-boot-part \ 111 | --efi-boot-image \ 112 | --protective-msdos-label \ 113 | ${SONAR_MOUNT_TARGET} \ 114 | -o ${SONAR_ISO_TARGET} 115 | 116 | ${LIMINE_DIR}/limine-deploy ${SONAR_ISO_TARGET} 117 | 118 | kernel: 119 | make -C ${SRC_DIR} 120 | 121 | clean: 122 | rm -rf ${BUILD_DIR}/* 123 | 124 | 125 | limine: 126 | cd ${BUILD_DIR} 127 | 128 | git clone ${LIMINE_REPO} --branch=${LIMINE_BRANCH} --depth=1 ${LIMINE_DIR} \ 129 | || echo "Limine already exists" 130 | make -C ${LIMINE_DIR} 131 | 132 | cd - 133 | -------------------------------------------------------------------------------- /src/protos/echo.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_H 2 | #define ECHO_H 3 | 4 | #include 5 | 6 | struct echo_stivale2_tag { 7 | uint64_t identifier; 8 | uint64_t next; 9 | } __attribute__((packed)); 10 | 11 | enum echo_flags { 12 | // Retrieve internal kernel state on kernel exit 13 | DUMP_KSTATE_ON_EXIT = (1 << 0), 14 | 15 | // Automatically write all echo variables to disk on kernel exit 16 | PRESERVE_VAR_ON_EXIT = (1 << 1), 17 | 18 | // Automatically write all echo snapshots to disk on kernel exit 19 | PRESERVE_SNAPSHOT_ON_EXIT = (1 << 2), 20 | }; 21 | 22 | enum echo_error_code { 23 | TODO = (1 << 2) 24 | }; 25 | 26 | struct stivale2_struct_tag_echo_info { 27 | struct echo_stivale2_tag tag; 28 | 29 | uint64_t warn; // Any warnings 30 | uint64_t err; // Any errors 31 | 32 | struct echo_runtime* services; // The echo runtime services 33 | } __attribute__((packed)); 34 | 35 | struct echo_regs { 36 | uint64_t rax, rbx, rcx, rdx, rsp, rbp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15, rip; 37 | uint64_t cs, ds, es, fs, gs, ss; 38 | uint64_t gdtr, idtr; 39 | } __attribute__((packed)); 40 | 41 | struct echo_stackframe { 42 | uint64_t addr; 43 | 44 | #define ECHO_STACKFRAME_NAME_LEN 24 45 | char name[ECHO_STACKFRAME_NAME_LEN]; 46 | 47 | struct echo_stackframe* next; 48 | } __attribute__((packed)); 49 | 50 | struct echo_table_hdr { 51 | uint64_t utid; 52 | 53 | #define ECHO_TABLE_HDR_NAME_LEN 8 54 | char name[ECHO_TABLE_HDR_NAME_LEN]; 55 | 56 | uint64_t len; 57 | } __attribute__((packed)); 58 | 59 | #define ECHO_VAR_NAME_LEN 16 60 | 61 | struct echo_var { 62 | uint64_t uvid; // Unique variable ID 63 | char name[ECHO_VAR_NAME_LEN]; 64 | 65 | uint64_t ctime; // Creation time 66 | 67 | uint64_t contents; 68 | } __attribute__((packed)); 69 | 70 | #define ECHO_SNAPSHOT_NAME_LEN 16 71 | 72 | struct echo_snapshot { 73 | uint64_t usid; // Unique snapshot ID 74 | 75 | char name[ECHO_SNAPSHOT_NAME_LEN]; 76 | 77 | uint64_t ctime; // Creation time 78 | 79 | struct echo_regs* regs; 80 | struct echo_frames* stacktrace; 81 | struct echo_tables* k_state; 82 | } __attribute__((packed)); 83 | 84 | struct echo_runtime { 85 | /* Hypervisor Services */ 86 | 87 | int swap_kernel(); 88 | 89 | int exit_kernel(); 90 | 91 | int install_handle(uint64_t type, 92 | void (*handler)(struct echo_runtime*)); 93 | 94 | int uninstall_handle(uint64_t type); 95 | 96 | int set_flag(uint64_t flag); 97 | 98 | int clear_flag(uint64_t flag); 99 | 100 | int get_flag(uint64_t flag); 101 | 102 | int set_kernel_state(struct echo_tbl_hdr* head); 103 | 104 | int get_kernel_state(struct echo_tbl_hdr* ret); 105 | 106 | /* Variable Services */ 107 | 108 | // Atomically create a new variable 109 | int echo_create_var(char* name, uint64_t val); 110 | 111 | // Write a variable to disk. Ideally, this should only be done in the 112 | // kernel destructor 113 | int echo_preserve_var(char* name); 114 | 115 | // Atomically store a new value in a variable 116 | int echo_update_var(char* name, uint64_t val); 117 | 118 | // Atomically retrieve a variable 119 | // Note that `ret` is a copy, not the actual variable 120 | int echo_get_var(char* name, struct echo_var* ret); 121 | 122 | // Atomically delete a variable 123 | int echo_delete_var(char* name); 124 | 125 | // Atomically rename a variable 126 | int echo_rename_var(char* name, char* new_name); 127 | 128 | /* Snapshot Services */ 129 | 130 | int echo_create_snapshot(char* name); 131 | 132 | int echo_preserve_snapshot(char* name); 133 | 134 | int echo_get_snapshot(char* name, struct echo_snapshot* ret); 135 | 136 | int echo_delete_snapshot(char* name); 137 | 138 | int echo_rename_snapshot(char* name, char* new_name); 139 | } __attribute__((packed)); 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sonar 2 | 3 | Sonar is a 64 bit Type-1 hypervisor aimed to implement kernel hotswapping. It should allow kernels to be easily tested by swapping them out and passing the saved state of the previous kernel to the current one. 4 | 5 | ![](docs/sonar.png) 6 | 7 | ## Project Map 8 | 9 | ### Source Directory Structure 10 | 11 | ``` 12 | src 13 | ├── boot : code that loads the kernel 14 | ├── drivers : kernel drivers 15 | ├── fs : filesystems 16 | ├── lib : kernel library routines 17 | ├── mm : kernel memory management 18 | ├── net : networking 19 | ├── ospm : OS power management 20 | ├── protos : boot protocols 21 | ├── sec : OS security 22 | ├── sys : architecture specific code 23 | ├── thirdparty : libraries that need to be compiled with the kernel 24 | └── virt : virtualization code 25 | ``` 26 | 27 | ### Build Directory Structure 28 | 29 | The tree below outlines an example structure of the build directory, which contains the binaries for sonar and its dependencies. 30 | 31 | ``` 32 | build 33 | ├── deps 34 | │   ├── bddisasm 35 | │   └── limine 36 | └── kernels 37 | ├── sonar 38 | │   └── objects 39 | └── test 40 | └── objects 41 | ``` 42 | 43 | ### Scripts 44 | 45 | ``` 46 | src 47 | ├── gensyms.sh : generates the kernel's symbol table for debugging 48 | ``` 49 | 50 | ## Building 51 | 52 | Building sonar is fairly simple. It only depends on a couple tools, most of which are provided by default in a Linux environment. They are as follows: 53 | - git 54 | - make 55 | - mkfs 56 | - clang 57 | - nasm 58 | 59 | ### Building and Running 60 | 61 | There are two possible methods for building: 62 | 1. Creating a full hard disk image 63 | 2. Creating the standalone Sonar kernel 64 | 65 | #### 1. Creating the Full Image 66 | 67 | This approach lets one create a full hard drive image, complete with sonar, the limine bootloader, and a test kernel. One can simply copy this image to a drive (flash drive, hard drive, etc) and then boot off this medium. 68 | 69 | Once the image (`sonar.img`) has been created, one can mount it then add/remove files and tweak the limine configuration to suit specific needs. The `sonar.img` file is generated like so: 70 | 71 | ``` 72 | make 73 | ``` 74 | 75 | The `make` command will automatically clone the dependencies and launch qemu. 76 | 77 | #### 2. Building the Standalone Kernel 78 | 79 | Before building the standalone kernel, be sure to clone and compile the dependencies first with 80 | 81 | ``` 82 | make deps 83 | ``` 84 | 85 | If the standalone sonar kernel image needs to be built for whatever reason (for use with a different bootloader, a different sized image, etc), it can be generated with the following command: 86 | 87 | ``` 88 | make sonar-kernel 89 | ``` 90 | 91 | The above command will compile a file called `ksonar.elf` in the sonar build directory, which can then be distributed. 92 | 93 | In addition, one can easily rebuild the test kernel only by executing the `test-kernel` rule. This will compile a file called `ktest.elf` in the test kernel build directory, which can then be copied into a hard disk image and loaded by sonar. 94 | 95 | ``` 96 | make test-kernel 97 | ``` 98 | 99 | ### Cleaning Up 100 | 101 | If, for whatever reason, the codebase needs to be rebuilt, it's often a better idea to only rebuilt the parts that need to be. Because of this, rules are provided to clean subprojects and dispose of remaining object files. They are as follows: 102 | 103 | ``` 104 | make clean-all 105 | 106 | make clean-kernels 107 | make clean-deps 108 | 109 | make clean-sonar 110 | make clean-test 111 | 112 | make clean-limine 113 | make clean-bddisasm 114 | make clean-lai 115 | ``` 116 | 117 | ## Dependencies 118 | 119 | - [Limine](https://github.com/limine-bootloader/limine) @ v0.6 120 | - [Bddisasm](https://github.com/bitdefender/bddisasm) @ v1.31.2 121 | - [LAI](https://github.com/managarm/lai) @ master 122 | 123 | ## License 124 | 125 | Copyright (c) 2021 Shreyas Lad 126 | 127 | Permission is hereby granted, free of charge, to any person obtaining a copy 128 | of this software and associated documentation files (the "Software"), to deal 129 | in the Software without restriction, including without limitation the rights 130 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 131 | copies of the Software, and to permit persons to whom the Software is 132 | furnished to do so, subject to the following conditions: 133 | 134 | The above copyright notice and this permission notice shall be included in all 135 | copies or substantial portions of the Software. 136 | 137 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 138 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 139 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 140 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 141 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 142 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 143 | SOFTWARE. -------------------------------------------------------------------------------- /src/lib/io.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_H 2 | #define IO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /////////////////////////////////////////////////////////////////////////////// 11 | // \author (c) Marco Paland (info@paland.com) 12 | // 2014-2019, PALANDesign Hannover, Germany 13 | // 14 | // \license The MIT License (MIT) 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in 24 | // all copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | // THE SOFTWARE. 33 | // 34 | // \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on 35 | // embedded systems with a very limited resources. 36 | // Use this instead of bloated standard/newlib printf. 37 | // These routines are thread safe and reentrant. 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | 42 | /** 43 | * Output a character to a custom device like UART, used by the printf() function 44 | * This function is declared here only. You have to write your custom implementation somewhere 45 | * \param character Character to output 46 | */ 47 | void _putchar(char character); 48 | 49 | 50 | /** 51 | * Tiny printf implementation 52 | * You have to implement _putchar if you use printf() 53 | * To avoid conflicts with the regular printf() API it is overridden by macro defines 54 | * and internal underscore-appended functions like printf_() are used 55 | * \param format A string that specifies the format of the output 56 | * \return The number of characters that are written into the array, not counting the terminating null character 57 | */ 58 | #define printf printf_ 59 | int printf_(const char* format, ...); 60 | 61 | 62 | /** 63 | * Tiny sprintf implementation 64 | * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! 65 | * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! 66 | * \param format A string that specifies the format of the output 67 | * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 68 | */ 69 | #define sprintf sprintf_ 70 | int sprintf_(char* buffer, const char* format, ...); 71 | 72 | 73 | /** 74 | * Tiny snprintf/vsnprintf implementation 75 | * \param buffer A pointer to the buffer where to store the formatted string 76 | * \param count The maximum number of characters to store in the buffer, including a terminating null character 77 | * \param format A string that specifies the format of the output 78 | * \param va A value identifying a variable arguments list 79 | * \return The number of characters that COULD have been written into the buffer, not counting the terminating 80 | * null character. A value equal or larger than count indicates truncation. Only when the returned value 81 | * is non-negative and less than count, the string has been completely written. 82 | */ 83 | #define snprintf snprintf_ 84 | #define vsnprintf vsnprintf_ 85 | int snprintf_(char* buffer, size_t count, const char* format, ...); 86 | int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); 87 | 88 | 89 | /** 90 | * Tiny vprintf implementation 91 | * \param format A string that specifies the format of the output 92 | * \param va A value identifying a variable arguments list 93 | * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 94 | */ 95 | #define vprintf vprintf_ 96 | int vprintf_(const char* format, va_list va); 97 | 98 | 99 | /** 100 | * printf with output function 101 | * You may use this as dynamic alternative to printf() with its fixed _putchar() output 102 | * \param out An output function which takes one character and an argument pointer 103 | * \param arg An argument pointer for user data passed to output function 104 | * \param format A string that specifies the format of the output 105 | * \return The number of characters that are sent to the output function, not counting the terminating null character 106 | */ 107 | int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /docs/ECHO.md: -------------------------------------------------------------------------------- 1 | # echo 2 | 3 | Echo is a specification that allows guest kernels to describe the current machine and internal state so that it may be stored and consequently restored at a later date. Echo compliant kernels are paravirtualized by some hypervisor, such as sonar, in order to be hotswappable. 4 | 5 | ## General Theory 6 | 7 | The system and internal state of the kernel should be stored in *some* format, that allows future kernels to easy parse and restore this saved state. Upon an update, the kernel should deploy a worker thread to fetch and determine the validity of a potentially new kernel image. If the image is determined to be new, the worker thread should signal the kernel to halt all execution and to dump the current state. The kernel will then load the image into memory and pass it through some kind of exposed interface so that the hypervisor may recieve it. The kernel will then transfer control to the hypervisor, at which point the hypervisor will load in the new kernel and virtualize it. 8 | 9 | When the hypervisor loads the new guest kernel, it will call the ctor that the kernel sets using the echo runtime services. This ctor is the one responsible for parsing saved state and loading it. Once it returns, the hypervisor will jump to the normal kernel entry point. 10 | 11 | If no ctor is provided, the hypervisor will simply jump to the kernel entry point. 12 | 13 | The hypervisor should expose it's existence through setting the Hypervisor Present Bit. 14 | 15 | ## Tables 16 | 17 | Sonar does not know the data or the format of the data that the kernel wishes to save in between launches, so it only provides a general method to store such data so that it can be passed on and interpreted by the next kernel. 18 | 19 | ```c 20 | struct echo_table_hdr { 21 | uint64_t utid; // Unique table identifier 22 | char name[8]; 23 | 24 | uint64_t* next; // Next table in the list 25 | 26 | uint64_t len; // Length in bytes 27 | uint64_t* data; 28 | } __attribute__((packed)); 29 | ``` 30 | 31 | The one exception to this, however, is the Resource Table. The Resource Table describes the new kernel to be loaded, providing the underlying hypervisor with all the information it needs to know to swap the current guest kernel out. The structure of this table is described below, and **must** be present in every 32 | 33 | ## Echo Information 34 | 35 | Echo is backwards compatible with stivale2 kernels. For this reason, it passes information to the virtualized kernel as a stivale2 tag. The ID of this tag and its structure is described below. Note that the kernel does not request any stivale2 tags related to echo; it only parses them. 36 | 37 | ```c 38 | #define STIVALE2_STRUCT_TAG_ECHO_INFO 39 | 40 | struct stivale2_struct_tag_echo_info { 41 | struct stivale2_tag tag; 42 | 43 | uint64_t warn; // Any warnings 44 | uint64_t err; // Any errors 45 | 46 | struct echo_runtime* services; // The echo runtime services 47 | } __attribute__((packed)); 48 | ``` 49 | 50 | Non-fatal warnings/errors don't prevent the kernel from loading, but may hamper some functionality or allude to problems. Echo reports non-fatal warnings/errors to the guest kernel in two ways: 51 | 1. Setting the `warn`/`err` field(s) in the echo information struct 52 | 2. Calling the error handlers that the kernel sets up 53 | 54 | It's recommended that the guest kernel install handlers for the kernel constructor (which is responsible for loading any saved data), the kernel destructor (which is responsible for saving internal state), and error handlers using `echo_register_handle` in the hypervisor runtime service. The underlying hypervisor will call these functions on its own when it sees fit. For example, it will independently call the kernel dtor when the guest kernel wants to create a snapshot *and* when the guest kernel wants to swap out. 55 | 56 | ## Error Codes 57 | 58 | Error and warning codes are bitwise OR'd with each other to form the `echo_err` and `echo_warn` fields. 59 | 60 | In addition, the guest kernel can use the echo runtime services to install custom error handlers for each type of error. If installed, these handlers will be called before the ctor so that the kernel can appropriatly handle them. 61 | 62 | It's recommended that the kernel either installs handlers or checks the `echo_err` field in the entry point. 63 | 64 | ```c 65 | enum echo_error_code { 66 | TODO = (1 << 2) 67 | }; 68 | ``` 69 | | Code | Description | 70 | | ---- | ----------- | 71 | | `(1 << 2)` | TODO | 72 | 73 | Notice that the first error code starts at 74 | 75 | ## Flags 76 | 77 | Flags allow the guest kernel to control various settings in the hypervisor's operation. A guest kernel can retrieve/modify flags by using the hypervisor runtime services. As with errors, flags are bitwise OR'd with each other, but this **is not** something that the guest kernel must worry about. The guest kernel sets/clears flags using the echo hypervisor services. 78 | 79 | ```c 80 | enum echo_flags { 81 | // Retrieve internal kernel state on kernel exit 82 | DUMP_KSTATE_ON_EXIT = (1 << 0), 83 | 84 | // Automatically write all echo variables to disk on kernel exit 85 | PRESERVE_VAR_ON_EXIT = (1 << 1), 86 | 87 | // Automatically write all echo snapshots to disk on kernel exit 88 | PRESERVE_SNAPSHOT_ON_EXIT = (1 << 2), 89 | }; 90 | ``` 91 | 92 | ## Variables 93 | 94 | Echo allocates space for guest kernels to specifically store information or flags in-between runs; these mechanisms are called echo variables. The Echo Runtime Services expose functions to create, update, and retrieve variables. Each variable is 8 bytes in length and is atomically written to/read from by the hypervisor. 95 | 96 | ```c 97 | struct echo_var { 98 | uint64_t uvid; // Unique variable ID 99 | char name[16]; 100 | 101 | uint64_t ctime; // Creation time 102 | 103 | uint64_t contents; 104 | } __attribute__((packed)); 105 | ``` 106 | 107 | ## Snapshots 108 | 109 | Echo provides a method for guest kernels to save state without swapping, and then either inspect or restore to that state. For instance, upon crashing, a kernel might save its state in a snapshot, switch out to a fresh version of the same kernel, and then inspect the faulting state of the machine. 110 | 111 | Each snapshot is created by saving the system state and then simply calling the kernel dtor. They are then assigned a unique tag and marked with a timestamp. The guest kernel can request all snapshots or a snapshot with a specific tag. 112 | 113 | #### Snapshot Structure 114 | 115 | ```c 116 | struct echo_snapshot { 117 | uint64_t usid; // Unique snapshot ID 118 | char name[16]; 119 | 120 | uint64_t ctime; // Creation time 121 | 122 | struct echo_regs* regs; 123 | struct echo_frames* stacktrace; 124 | struct echo_tables* k_state; 125 | } __attribute__((packed)); 126 | ``` 127 | 128 | #### Registers Structure 129 | 130 | ```c 131 | struct echo_regs { 132 | uint64_t rax, rbx, rcx, rdx, rsp, rbp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15, rip; 133 | uint64_t cs, ds, es, fs, gs, ss; 134 | uint64_t gdtr, idtr; 135 | } __attribute__((packed)); 136 | ``` 137 | 138 | #### Stackframe Structure 139 | 140 | ```c 141 | struct echo_stackframe { 142 | uint64_t addr; 143 | char name[24]; 144 | 145 | struct echo_stackframe* next; 146 | } __attribute__((packed)); 147 | ``` 148 | 149 | ## Runtime Services 150 | 151 | ```c 152 | struct echo_runtime { 153 | /* Hypervisor Services */ 154 | ... 155 | 156 | 157 | /* Variable Services */ 158 | ... 159 | 160 | /* Snapshot Services */ 161 | ... 162 | }; 163 | ``` 164 | 165 | #### Hypervisor Services 166 | 167 | ```c 168 | int swap_kernel(); 169 | 170 | int exit_kernel(); 171 | 172 | int install_handle(uint64_t type, 173 | void (*handler)(struct echo_runtime_servs*)); 174 | 175 | int uninstall_handle(uint64_t type); 176 | 177 | int set_flag(uint64_t flag); 178 | 179 | int clear_flag(uint64_t flag); 180 | 181 | int get_flag(uint64_t flag); 182 | 183 | int set_kernel_state(struct echo_tbl_hdr* head); 184 | 185 | int get_kernel_state(struct echo_tbl_hdr* ret); 186 | ``` 187 | 188 | ##### Handler Types 189 | 190 | Handler IDs are used when registering handlers for different tasks. The underlying hypervisor will invoke these handlers on its own when it sees fit, and their only job is to 191 | 192 | ```c 193 | enum echo_handler_type { 194 | K_CTOR = (1 << 0), // The handle is a kernel constructor 195 | K_DTOR = (1 << 1) // The handle is a kernel destructor 196 | }; 197 | ``` 198 | 199 | #### Variable Services 200 | 201 | TODO: Provide some way to enumerate over variables and snapshots 202 | TODO: Provide a bitmap to apply the same operation to a number of variables/snapshots 203 | 204 | ```c 205 | // Atomically create a new variable 206 | int echo_create_var(char* name, uint64_t val); 207 | 208 | // Write a variable to disk. Ideally, this should only be done in the 209 | // kernel destructor 210 | int echo_preserve_var(char* name); 211 | 212 | // Atomically store a new value in a variable 213 | int echo_update_var(char* name, uint64_t val); 214 | 215 | // Atomically retrieve a variable 216 | // Note that `ret` is a copy, not the actual variable 217 | int echo_get_var(char* name, struct echo_var* ret); 218 | 219 | // Atomically delete a variable 220 | int echo_delete_var(char* name); 221 | 222 | // Atomically rename a variable 223 | int echo_rename_var(char* name, char* new_name); 224 | ``` 225 | 226 | #### Snapshot Services 227 | 228 | ```c 229 | int echo_create_snapshot(char* name); 230 | 231 | int echo_preserve_snapshot(char* name); 232 | 233 | int echo_get_snapshot(char* name, struct echo_snapshot* ret); 234 | 235 | int echo_delete_snapshot(char* name); 236 | 237 | int echo_rename_snapshot(char* name, char* new_name); 238 | ``` -------------------------------------------------------------------------------- /src/protos/limine.h: -------------------------------------------------------------------------------- 1 | /* BSD Zero Clause License */ 2 | 3 | /* Copyright (C) 2022 mintsuki and contributors. 4 | * 5 | * Permission to use, copy, modify, and/or distribute this software for any 6 | * purpose with or without fee is hereby granted. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef _LIMINE_H 18 | #define _LIMINE_H 1 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #include 25 | 26 | /* Misc */ 27 | 28 | #ifdef LIMINE_NO_POINTERS 29 | # define LIMINE_PTR(TYPE) uint64_t 30 | #else 31 | # define LIMINE_PTR(TYPE) TYPE 32 | #endif 33 | 34 | #define LIMINE_COMMON_MAGIC 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b 35 | 36 | struct limine_uuid { 37 | uint32_t a; 38 | uint16_t b; 39 | uint16_t c; 40 | uint8_t d[8]; 41 | }; 42 | 43 | #define LIMINE_MEDIA_TYPE_GENERIC 0 44 | #define LIMINE_MEDIA_TYPE_OPTICAL 1 45 | #define LIMINE_MEDIA_TYPE_TFTP 2 46 | 47 | struct limine_file { 48 | uint64_t revision; 49 | LIMINE_PTR(void *) address; 50 | uint64_t size; 51 | LIMINE_PTR(char *) path; 52 | LIMINE_PTR(char *) cmdline; 53 | uint32_t media_type; 54 | uint32_t unused; 55 | uint32_t tftp_ip; 56 | uint32_t tftp_port; 57 | uint32_t partition_index; 58 | uint32_t mbr_disk_id; 59 | struct limine_uuid gpt_disk_uuid; 60 | struct limine_uuid gpt_part_uuid; 61 | struct limine_uuid part_uuid; 62 | }; 63 | 64 | /* Boot info */ 65 | 66 | #define LIMINE_BOOTLOADER_INFO_REQUEST { LIMINE_COMMON_MAGIC, 0xf55038d8e2a1202f, 0x279426fcf5f59740 } 67 | 68 | struct limine_bootloader_info_response { 69 | uint64_t revision; 70 | LIMINE_PTR(char *) name; 71 | LIMINE_PTR(char *) version; 72 | }; 73 | 74 | struct limine_bootloader_info_request { 75 | uint64_t id[4]; 76 | uint64_t revision; 77 | LIMINE_PTR(struct limine_bootloader_info_response *) response; 78 | }; 79 | 80 | /* Stack size */ 81 | 82 | #define LIMINE_STACK_SIZE_REQUEST { LIMINE_COMMON_MAGIC, 0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d } 83 | 84 | struct limine_stack_size_response { 85 | uint64_t revision; 86 | }; 87 | 88 | struct limine_stack_size_request { 89 | uint64_t id[4]; 90 | uint64_t revision; 91 | LIMINE_PTR(struct limine_stack_size_response *) response; 92 | uint64_t stack_size; 93 | }; 94 | 95 | /* HHDM */ 96 | 97 | #define LIMINE_HHDM_REQUEST { LIMINE_COMMON_MAGIC, 0x48dcf1cb8ad2b852, 0x63984e959a98244b } 98 | 99 | struct limine_hhdm_response { 100 | uint64_t revision; 101 | uint64_t offset; 102 | }; 103 | 104 | struct limine_hhdm_request { 105 | uint64_t id[4]; 106 | uint64_t revision; 107 | LIMINE_PTR(struct limine_hhdm_response *) response; 108 | }; 109 | 110 | /* Framebuffer */ 111 | 112 | #define LIMINE_FRAMEBUFFER_REQUEST { LIMINE_COMMON_MAGIC, 0x9d5827dcd881dd75, 0xa3148604f6fab11b } 113 | 114 | #define LIMINE_FRAMEBUFFER_RGB 1 115 | 116 | struct limine_video_mode { 117 | uint64_t pitch; 118 | uint64_t width; 119 | uint64_t height; 120 | uint16_t bpp; 121 | uint8_t memory_model; 122 | uint8_t red_mask_size; 123 | uint8_t red_mask_shift; 124 | uint8_t green_mask_size; 125 | uint8_t green_mask_shift; 126 | uint8_t blue_mask_size; 127 | uint8_t blue_mask_shift; 128 | }; 129 | 130 | struct limine_framebuffer { 131 | LIMINE_PTR(void *) address; 132 | uint64_t width; 133 | uint64_t height; 134 | uint64_t pitch; 135 | uint16_t bpp; 136 | uint8_t memory_model; 137 | uint8_t red_mask_size; 138 | uint8_t red_mask_shift; 139 | uint8_t green_mask_size; 140 | uint8_t green_mask_shift; 141 | uint8_t blue_mask_size; 142 | uint8_t blue_mask_shift; 143 | uint8_t unused[7]; 144 | uint64_t edid_size; 145 | LIMINE_PTR(void *) edid; 146 | /* Revision 1 */ 147 | uint64_t mode_count; 148 | LIMINE_PTR(struct limine_video_mode **) modes; 149 | }; 150 | 151 | struct limine_framebuffer_response { 152 | uint64_t revision; 153 | uint64_t framebuffer_count; 154 | LIMINE_PTR(struct limine_framebuffer **) framebuffers; 155 | }; 156 | 157 | struct limine_framebuffer_request { 158 | uint64_t id[4]; 159 | uint64_t revision; 160 | LIMINE_PTR(struct limine_framebuffer_response *) response; 161 | }; 162 | 163 | /* Terminal */ 164 | 165 | #define LIMINE_TERMINAL_REQUEST { LIMINE_COMMON_MAGIC, 0xc8ac59310c2b0844, 0xa68d0c7265d38878 } 166 | 167 | #define LIMINE_TERMINAL_CB_DEC 10 168 | #define LIMINE_TERMINAL_CB_BELL 20 169 | #define LIMINE_TERMINAL_CB_PRIVATE_ID 30 170 | #define LIMINE_TERMINAL_CB_STATUS_REPORT 40 171 | #define LIMINE_TERMINAL_CB_POS_REPORT 50 172 | #define LIMINE_TERMINAL_CB_KBD_LEDS 60 173 | #define LIMINE_TERMINAL_CB_MODE 70 174 | #define LIMINE_TERMINAL_CB_LINUX 80 175 | 176 | #define LIMINE_TERMINAL_CTX_SIZE ((uint64_t)(-1)) 177 | #define LIMINE_TERMINAL_CTX_SAVE ((uint64_t)(-2)) 178 | #define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3)) 179 | #define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4)) 180 | 181 | struct limine_terminal; 182 | 183 | typedef void (*limine_terminal_write)(struct limine_terminal *, const char *, uint64_t); 184 | typedef void (*limine_terminal_callback)(struct limine_terminal *, uint64_t, uint64_t, uint64_t, uint64_t); 185 | 186 | struct limine_terminal { 187 | uint64_t columns; 188 | uint64_t rows; 189 | LIMINE_PTR(struct limine_framebuffer *) framebuffer; 190 | }; 191 | 192 | struct limine_terminal_response { 193 | uint64_t revision; 194 | uint64_t terminal_count; 195 | LIMINE_PTR(struct limine_terminal **) terminals; 196 | LIMINE_PTR(limine_terminal_write) write; 197 | }; 198 | 199 | struct limine_terminal_request { 200 | uint64_t id[4]; 201 | uint64_t revision; 202 | LIMINE_PTR(struct limine_terminal_response *) response; 203 | LIMINE_PTR(limine_terminal_callback) callback; 204 | }; 205 | 206 | /* 5-level paging */ 207 | 208 | #define LIMINE_5_LEVEL_PAGING_REQUEST { LIMINE_COMMON_MAGIC, 0x94469551da9b3192, 0xebe5e86db7382888 } 209 | 210 | struct limine_5_level_paging_response { 211 | uint64_t revision; 212 | }; 213 | 214 | struct limine_5_level_paging_request { 215 | uint64_t id[4]; 216 | uint64_t revision; 217 | LIMINE_PTR(struct limine_5_level_paging_response *) response; 218 | }; 219 | 220 | /* SMP */ 221 | 222 | #define LIMINE_SMP_REQUEST { LIMINE_COMMON_MAGIC, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0 } 223 | 224 | struct limine_smp_info; 225 | 226 | typedef void (*limine_goto_address)(struct limine_smp_info *); 227 | 228 | #if defined (__x86_64__) || defined (__i386__) 229 | 230 | #define LIMINE_SMP_X2APIC (1 << 0) 231 | 232 | struct limine_smp_info { 233 | uint32_t processor_id; 234 | uint32_t lapic_id; 235 | uint64_t reserved; 236 | LIMINE_PTR(limine_goto_address) goto_address; 237 | uint64_t extra_argument; 238 | }; 239 | 240 | struct limine_smp_response { 241 | uint64_t revision; 242 | uint32_t flags; 243 | uint32_t bsp_lapic_id; 244 | uint64_t cpu_count; 245 | LIMINE_PTR(struct limine_smp_info **) cpus; 246 | }; 247 | 248 | #elif defined (__aarch64__) 249 | 250 | struct limine_smp_info { 251 | uint32_t processor_id; 252 | uint32_t gic_iface_no; 253 | uint64_t mpidr; 254 | uint64_t reserved; 255 | LIMINE_PTR(limine_goto_address) goto_address; 256 | uint64_t extra_argument; 257 | }; 258 | 259 | struct limine_smp_response { 260 | uint64_t revision; 261 | uint32_t flags; 262 | uint64_t bsp_mpidr; 263 | uint64_t cpu_count; 264 | LIMINE_PTR(struct limine_smp_info **) cpus; 265 | }; 266 | 267 | #else 268 | #error Unknown architecture 269 | #endif 270 | 271 | struct limine_smp_request { 272 | uint64_t id[4]; 273 | uint64_t revision; 274 | LIMINE_PTR(struct limine_smp_response *) response; 275 | uint64_t flags; 276 | }; 277 | 278 | /* Memory map */ 279 | 280 | #define LIMINE_MEMMAP_REQUEST { LIMINE_COMMON_MAGIC, 0x67cf3d9d378a806f, 0xe304acdfc50c3c62 } 281 | 282 | #define LIMINE_MEMMAP_USABLE 0 283 | #define LIMINE_MEMMAP_RESERVED 1 284 | #define LIMINE_MEMMAP_ACPI_RECLAIMABLE 2 285 | #define LIMINE_MEMMAP_ACPI_NVS 3 286 | #define LIMINE_MEMMAP_BAD_MEMORY 4 287 | #define LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE 5 288 | #define LIMINE_MEMMAP_KERNEL_AND_MODULES 6 289 | #define LIMINE_MEMMAP_FRAMEBUFFER 7 290 | 291 | struct limine_memmap_entry { 292 | uint64_t base; 293 | uint64_t length; 294 | uint64_t type; 295 | }; 296 | 297 | struct limine_memmap_response { 298 | uint64_t revision; 299 | uint64_t entry_count; 300 | LIMINE_PTR(struct limine_memmap_entry **) entries; 301 | }; 302 | 303 | struct limine_memmap_request { 304 | uint64_t id[4]; 305 | uint64_t revision; 306 | LIMINE_PTR(struct limine_memmap_response *) response; 307 | }; 308 | 309 | /* Entry point */ 310 | 311 | #define LIMINE_ENTRY_POINT_REQUEST { LIMINE_COMMON_MAGIC, 0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a } 312 | 313 | typedef void (*limine_entry_point)(void); 314 | 315 | struct limine_entry_point_response { 316 | uint64_t revision; 317 | }; 318 | 319 | struct limine_entry_point_request { 320 | uint64_t id[4]; 321 | uint64_t revision; 322 | LIMINE_PTR(struct limine_entry_point_response *) response; 323 | LIMINE_PTR(limine_entry_point) entry; 324 | }; 325 | 326 | /* Kernel File */ 327 | 328 | #define LIMINE_KERNEL_FILE_REQUEST { LIMINE_COMMON_MAGIC, 0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69 } 329 | 330 | struct limine_kernel_file_response { 331 | uint64_t revision; 332 | LIMINE_PTR(struct limine_file *) kernel_file; 333 | }; 334 | 335 | struct limine_kernel_file_request { 336 | uint64_t id[4]; 337 | uint64_t revision; 338 | LIMINE_PTR(struct limine_kernel_file_response *) response; 339 | }; 340 | 341 | /* Module */ 342 | 343 | #define LIMINE_MODULE_REQUEST { LIMINE_COMMON_MAGIC, 0x3e7e279702be32af, 0xca1c4f3bd1280cee } 344 | 345 | struct limine_module_response { 346 | uint64_t revision; 347 | uint64_t module_count; 348 | LIMINE_PTR(struct limine_file **) modules; 349 | }; 350 | 351 | struct limine_module_request { 352 | uint64_t id[4]; 353 | uint64_t revision; 354 | LIMINE_PTR(struct limine_module_response *) response; 355 | }; 356 | 357 | /* RSDP */ 358 | 359 | #define LIMINE_RSDP_REQUEST { LIMINE_COMMON_MAGIC, 0xc5e77b6b397e7b43, 0x27637845accdcf3c } 360 | 361 | struct limine_rsdp_response { 362 | uint64_t revision; 363 | LIMINE_PTR(void *) address; 364 | }; 365 | 366 | struct limine_rsdp_request { 367 | uint64_t id[4]; 368 | uint64_t revision; 369 | LIMINE_PTR(struct limine_rsdp_response *) response; 370 | }; 371 | 372 | /* SMBIOS */ 373 | 374 | #define LIMINE_SMBIOS_REQUEST { LIMINE_COMMON_MAGIC, 0x9e9046f11e095391, 0xaa4a520fefbde5ee } 375 | 376 | struct limine_smbios_response { 377 | uint64_t revision; 378 | LIMINE_PTR(void *) entry_32; 379 | LIMINE_PTR(void *) entry_64; 380 | }; 381 | 382 | struct limine_smbios_request { 383 | uint64_t id[4]; 384 | uint64_t revision; 385 | LIMINE_PTR(struct limine_smbios_response *) response; 386 | }; 387 | 388 | /* EFI system table */ 389 | 390 | #define LIMINE_EFI_SYSTEM_TABLE_REQUEST { LIMINE_COMMON_MAGIC, 0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc } 391 | 392 | struct limine_efi_system_table_response { 393 | uint64_t revision; 394 | LIMINE_PTR(void *) address; 395 | }; 396 | 397 | struct limine_efi_system_table_request { 398 | uint64_t id[4]; 399 | uint64_t revision; 400 | LIMINE_PTR(struct limine_efi_system_table_response *) response; 401 | }; 402 | 403 | /* Boot time */ 404 | 405 | #define LIMINE_BOOT_TIME_REQUEST { LIMINE_COMMON_MAGIC, 0x502746e184c088aa, 0xfbc5ec83e6327893 } 406 | 407 | struct limine_boot_time_response { 408 | uint64_t revision; 409 | int64_t boot_time; 410 | }; 411 | 412 | struct limine_boot_time_request { 413 | uint64_t id[4]; 414 | uint64_t revision; 415 | LIMINE_PTR(struct limine_boot_time_response *) response; 416 | }; 417 | 418 | /* Kernel address */ 419 | 420 | #define LIMINE_KERNEL_ADDRESS_REQUEST { LIMINE_COMMON_MAGIC, 0x71ba76863cc55f63, 0xb2644a48c516a487 } 421 | 422 | struct limine_kernel_address_response { 423 | uint64_t revision; 424 | uint64_t physical_base; 425 | uint64_t virtual_base; 426 | }; 427 | 428 | struct limine_kernel_address_request { 429 | uint64_t id[4]; 430 | uint64_t revision; 431 | LIMINE_PTR(struct limine_kernel_address_response *) response; 432 | }; 433 | 434 | /* Device Tree Blob */ 435 | 436 | #define LIMINE_DTB_REQUEST { LIMINE_COMMON_MAGIC, 0xb40ddb48fb54bac7, 0x545081493f81ffb7 } 437 | 438 | struct limine_dtb_response { 439 | uint64_t revision; 440 | LIMINE_PTR(void *) dtb_ptr; 441 | }; 442 | 443 | struct limine_dtb_request { 444 | uint64_t id[4]; 445 | uint64_t revision; 446 | LIMINE_PTR(struct limine_dtb_response *) response; 447 | }; 448 | 449 | #ifdef __cplusplus 450 | } 451 | #endif 452 | 453 | #endif 454 | -------------------------------------------------------------------------------- /src/lib/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void _putchar(char c) { 4 | serial_write(c); 5 | } 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // \author (c) Marco Paland (info@paland.com) 9 | // 2014-2019, PALANDesign Hannover, Germany 10 | // 11 | // \license The MIT License (MIT) 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | // 31 | // \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on 32 | // embedded systems with a very limited resources. These routines are thread 33 | // safe and reentrant! 34 | // Use this instead of the bloated standard/newlib printf cause these use 35 | // malloc for printf (and may not be thread safe). 36 | // 37 | /////////////////////////////////////////////////////////////////////////////// 38 | 39 | #include 40 | #include 41 | 42 | // define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the 43 | // printf_config.h header file 44 | // default: undefined 45 | #ifdef PRINTF_INCLUDE_CONFIG_H 46 | #include "printf_config.h" 47 | #endif 48 | 49 | 50 | // 'ntoa' conversion buffer size, this must be big enough to hold one converted 51 | // numeric number including padded zeros (dynamically created on stack) 52 | // default: 32 byte 53 | #ifndef PRINTF_NTOA_BUFFER_SIZE 54 | #define PRINTF_NTOA_BUFFER_SIZE 32U 55 | #endif 56 | 57 | // 'ftoa' conversion buffer size, this must be big enough to hold one converted 58 | // float number including padded zeros (dynamically created on stack) 59 | // default: 32 byte 60 | #ifndef PRINTF_FTOA_BUFFER_SIZE 61 | #define PRINTF_FTOA_BUFFER_SIZE 32U 62 | #endif 63 | 64 | // support for the floating point type (%f) 65 | // default: activated 66 | #ifndef PRINTF_DISABLE_SUPPORT_FLOAT 67 | #define PRINTF_SUPPORT_FLOAT 68 | #endif 69 | 70 | // support for exponential floating point notation (%e/%g) 71 | // default: activated 72 | #ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL 73 | #define PRINTF_SUPPORT_EXPONENTIAL 74 | #endif 75 | 76 | // define the default floating point precision 77 | // default: 6 digits 78 | #ifndef PRINTF_DEFAULT_FLOAT_PRECISION 79 | #define PRINTF_DEFAULT_FLOAT_PRECISION 6U 80 | #endif 81 | 82 | // define the largest float suitable to print with %f 83 | // default: 1e9 84 | #ifndef PRINTF_MAX_FLOAT 85 | #define PRINTF_MAX_FLOAT 1e9 86 | #endif 87 | 88 | // support for the long long types (%llu or %p) 89 | // default: activated 90 | #ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG 91 | #define PRINTF_SUPPORT_LONG_LONG 92 | #endif 93 | 94 | // support for the ptrdiff_t type (%t) 95 | // ptrdiff_t is normally defined in as long or long long type 96 | // default: activated 97 | #ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T 98 | #define PRINTF_SUPPORT_PTRDIFF_T 99 | #endif 100 | 101 | /////////////////////////////////////////////////////////////////////////////// 102 | 103 | // internal flag definitions 104 | #define FLAGS_ZEROPAD (1U << 0U) 105 | #define FLAGS_LEFT (1U << 1U) 106 | #define FLAGS_PLUS (1U << 2U) 107 | #define FLAGS_SPACE (1U << 3U) 108 | #define FLAGS_HASH (1U << 4U) 109 | #define FLAGS_UPPERCASE (1U << 5U) 110 | #define FLAGS_CHAR (1U << 6U) 111 | #define FLAGS_SHORT (1U << 7U) 112 | #define FLAGS_LONG (1U << 8U) 113 | #define FLAGS_LONG_LONG (1U << 9U) 114 | #define FLAGS_PRECISION (1U << 10U) 115 | #define FLAGS_ADAPT_EXP (1U << 11U) 116 | 117 | #undef PRINTF_SUPPORT_FLOAT 118 | // import float.h for DBL_MAX 119 | #if defined(PRINTF_SUPPORT_FLOAT) 120 | #include 121 | #endif 122 | 123 | 124 | // output function type 125 | typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); 126 | 127 | 128 | // wrapper (used as buffer) for output function type 129 | typedef struct { 130 | void (*fct)(char character, void* arg); 131 | void* arg; 132 | } out_fct_wrap_type; 133 | 134 | // internal buffer output 135 | static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) 136 | { 137 | if (idx < maxlen) { 138 | ((char*)buffer)[idx] = character; 139 | } 140 | } 141 | 142 | 143 | // internal null output 144 | static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) 145 | { 146 | (void)character; (void)buffer; (void)idx; (void)maxlen; 147 | } 148 | 149 | 150 | // internal _putchar wrapper 151 | void _out_char(char character, void* buffer, size_t idx, size_t maxlen) 152 | { 153 | (void)buffer; (void)idx; (void)maxlen; 154 | if (character) { 155 | _putchar(character); 156 | } 157 | } 158 | 159 | 160 | // internal output function wrapper 161 | static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) 162 | { 163 | (void)idx; (void)maxlen; 164 | if (character) { 165 | // buffer is the output fct pointer 166 | ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); 167 | } 168 | } 169 | 170 | 171 | // internal secure strlen 172 | // \return The length of the string (excluding the terminating 0) limited by 'maxsize' 173 | static inline unsigned int _strnlen_s(const char* str, size_t maxsize) 174 | { 175 | const char* s; 176 | for (s = str; *s && maxsize--; ++s); 177 | return (unsigned int)(s - str); 178 | } 179 | 180 | 181 | // internal test if char is a digit (0-9) 182 | // \return true if char is a digit 183 | static inline bool _is_digit(char ch) 184 | { 185 | return (ch >= '0') && (ch <= '9'); 186 | } 187 | 188 | 189 | // internal ASCII string to unsigned int conversion 190 | static unsigned int _atoi(const char** str) 191 | { 192 | unsigned int i = 0U; 193 | while (_is_digit(**str)) { 194 | i = i * 10U + (unsigned int)(*((*str)++) - '0'); 195 | } 196 | return i; 197 | } 198 | 199 | 200 | // output the specified string in reverse, taking care of any zero-padding 201 | static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) 202 | { 203 | const size_t start_idx = idx; 204 | 205 | // pad spaces up to given width 206 | if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { 207 | for (size_t i = len; i < width; i++) { 208 | out(' ', buffer, idx++, maxlen); 209 | } 210 | } 211 | 212 | // reverse string 213 | while (len) { 214 | out(buf[--len], buffer, idx++, maxlen); 215 | } 216 | 217 | // append pad spaces up to given width 218 | if (flags & FLAGS_LEFT) { 219 | while (idx - start_idx < width) { 220 | out(' ', buffer, idx++, maxlen); 221 | } 222 | } 223 | 224 | return idx; 225 | } 226 | 227 | 228 | // internal itoa format 229 | static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) 230 | { 231 | // pad leading zeros 232 | if (!(flags & FLAGS_LEFT)) { 233 | if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { 234 | width--; 235 | } 236 | while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { 237 | buf[len++] = '0'; 238 | } 239 | while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { 240 | buf[len++] = '0'; 241 | } 242 | } 243 | 244 | // handle hash 245 | if (flags & FLAGS_HASH) { 246 | if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { 247 | len--; 248 | if (len && (base == 16U)) { 249 | len--; 250 | } 251 | } 252 | if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { 253 | buf[len++] = 'x'; 254 | } 255 | else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { 256 | buf[len++] = 'X'; 257 | } 258 | else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { 259 | buf[len++] = 'b'; 260 | } 261 | if (len < PRINTF_NTOA_BUFFER_SIZE) { 262 | buf[len++] = '0'; 263 | } 264 | } 265 | 266 | if (len < PRINTF_NTOA_BUFFER_SIZE) { 267 | if (negative) { 268 | buf[len++] = '-'; 269 | } 270 | else if (flags & FLAGS_PLUS) { 271 | buf[len++] = '+'; // ignore the space if the '+' exists 272 | } 273 | else if (flags & FLAGS_SPACE) { 274 | buf[len++] = ' '; 275 | } 276 | } 277 | 278 | return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); 279 | } 280 | 281 | 282 | // internal itoa for 'long' type 283 | static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) 284 | { 285 | char buf[PRINTF_NTOA_BUFFER_SIZE]; 286 | size_t len = 0U; 287 | 288 | // no hash for 0 values 289 | if (!value) { 290 | flags &= ~FLAGS_HASH; 291 | } 292 | 293 | // write if precision != 0 and value is != 0 294 | if (!(flags & FLAGS_PRECISION) || value) { 295 | do { 296 | const char digit = (char)(value % base); 297 | buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; 298 | value /= base; 299 | } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); 300 | } 301 | 302 | return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); 303 | } 304 | 305 | 306 | // internal itoa for 'long long' type 307 | #if defined(PRINTF_SUPPORT_LONG_LONG) 308 | static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) 309 | { 310 | char buf[PRINTF_NTOA_BUFFER_SIZE]; 311 | size_t len = 0U; 312 | 313 | // no hash for 0 values 314 | if (!value) { 315 | flags &= ~FLAGS_HASH; 316 | } 317 | 318 | // write if precision != 0 and value is != 0 319 | if (!(flags & FLAGS_PRECISION) || value) { 320 | do { 321 | const char digit = (char)(value % base); 322 | buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; 323 | value /= base; 324 | } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); 325 | } 326 | 327 | return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); 328 | } 329 | #endif // PRINTF_SUPPORT_LONG_LONG 330 | 331 | 332 | #if defined(PRINTF_SUPPORT_FLOAT) 333 | 334 | #if defined(PRINTF_SUPPORT_EXPONENTIAL) 335 | // forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT 336 | static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); 337 | #endif 338 | 339 | 340 | // internal ftoa for fixed decimal floating point 341 | static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) 342 | { 343 | char buf[PRINTF_FTOA_BUFFER_SIZE]; 344 | size_t len = 0U; 345 | double diff = 0.0; 346 | 347 | // powers of 10 348 | static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; 349 | 350 | // test for special values 351 | if (value != value) 352 | return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); 353 | if (value < -DBL_MAX) 354 | return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); 355 | if (value > DBL_MAX) 356 | return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); 357 | 358 | // test for very large values 359 | // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad 360 | if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { 361 | #if defined(PRINTF_SUPPORT_EXPONENTIAL) 362 | return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); 363 | #else 364 | return 0U; 365 | #endif 366 | } 367 | 368 | // test for negative 369 | bool negative = false; 370 | if (value < 0) { 371 | negative = true; 372 | value = 0 - value; 373 | } 374 | 375 | // set default precision, if not set explicitly 376 | if (!(flags & FLAGS_PRECISION)) { 377 | prec = PRINTF_DEFAULT_FLOAT_PRECISION; 378 | } 379 | // limit precision to 9, cause a prec >= 10 can lead to overflow errors 380 | while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { 381 | buf[len++] = '0'; 382 | prec--; 383 | } 384 | 385 | int whole = (int)value; 386 | double tmp = (value - whole) * pow10[prec]; 387 | unsigned long frac = (unsigned long)tmp; 388 | diff = tmp - frac; 389 | 390 | if (diff > 0.5) { 391 | ++frac; 392 | // handle rollover, e.g. case 0.99 with prec 1 is 1.0 393 | if (frac >= pow10[prec]) { 394 | frac = 0; 395 | ++whole; 396 | } 397 | } 398 | else if (diff < 0.5) { 399 | } 400 | else if ((frac == 0U) || (frac & 1U)) { 401 | // if halfway, round up if odd OR if last digit is 0 402 | ++frac; 403 | } 404 | 405 | if (prec == 0U) { 406 | diff = value - (double)whole; 407 | if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { 408 | // exactly 0.5 and ODD, then round up 409 | // 1.5 -> 2, but 2.5 -> 2 410 | ++whole; 411 | } 412 | } 413 | else { 414 | unsigned int count = prec; 415 | // now do fractional part, as an unsigned number 416 | while (len < PRINTF_FTOA_BUFFER_SIZE) { 417 | --count; 418 | buf[len++] = (char)(48U + (frac % 10U)); 419 | if (!(frac /= 10U)) { 420 | break; 421 | } 422 | } 423 | // add extra 0s 424 | while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { 425 | buf[len++] = '0'; 426 | } 427 | if (len < PRINTF_FTOA_BUFFER_SIZE) { 428 | // add decimal 429 | buf[len++] = '.'; 430 | } 431 | } 432 | 433 | // do whole part, number is reversed 434 | while (len < PRINTF_FTOA_BUFFER_SIZE) { 435 | buf[len++] = (char)(48 + (whole % 10)); 436 | if (!(whole /= 10)) { 437 | break; 438 | } 439 | } 440 | 441 | // pad leading zeros 442 | if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { 443 | if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { 444 | width--; 445 | } 446 | while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { 447 | buf[len++] = '0'; 448 | } 449 | } 450 | 451 | if (len < PRINTF_FTOA_BUFFER_SIZE) { 452 | if (negative) { 453 | buf[len++] = '-'; 454 | } 455 | else if (flags & FLAGS_PLUS) { 456 | buf[len++] = '+'; // ignore the space if the '+' exists 457 | } 458 | else if (flags & FLAGS_SPACE) { 459 | buf[len++] = ' '; 460 | } 461 | } 462 | 463 | return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); 464 | } 465 | 466 | 467 | #if defined(PRINTF_SUPPORT_EXPONENTIAL) 468 | // internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse 469 | static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) 470 | { 471 | // check for NaN and special values 472 | if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { 473 | return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); 474 | } 475 | 476 | // determine the sign 477 | const bool negative = value < 0; 478 | if (negative) { 479 | value = -value; 480 | } 481 | 482 | // default precision 483 | if (!(flags & FLAGS_PRECISION)) { 484 | prec = PRINTF_DEFAULT_FLOAT_PRECISION; 485 | } 486 | 487 | // determine the decimal exponent 488 | // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) 489 | union { 490 | uint64_t U; 491 | double F; 492 | } conv; 493 | 494 | conv.F = value; 495 | int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 496 | conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) 497 | // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 498 | int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); 499 | // now we want to compute 10^expval but we want to be sure it won't overflow 500 | exp2 = (int)(expval * 3.321928094887362 + 0.5); 501 | const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; 502 | const double z2 = z * z; 503 | conv.U = (uint64_t)(exp2 + 1023) << 52U; 504 | // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex 505 | conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); 506 | // correct for rounding errors 507 | if (value < conv.F) { 508 | expval--; 509 | conv.F /= 10; 510 | } 511 | 512 | // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters 513 | unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; 514 | 515 | // in "%g" mode, "prec" is the number of *significant figures* not decimals 516 | if (flags & FLAGS_ADAPT_EXP) { 517 | // do we want to fall-back to "%f" mode? 518 | if ((value >= 1e-4) && (value < 1e6)) { 519 | if ((int)prec > expval) { 520 | prec = (unsigned)((int)prec - expval - 1); 521 | } 522 | else { 523 | prec = 0; 524 | } 525 | flags |= FLAGS_PRECISION; // make sure _ftoa respects precision 526 | // no characters in exponent 527 | minwidth = 0U; 528 | expval = 0; 529 | } 530 | else { 531 | // we use one sigfig for the whole part 532 | if ((prec > 0) && (flags & FLAGS_PRECISION)) { 533 | --prec; 534 | } 535 | } 536 | } 537 | 538 | // will everything fit? 539 | unsigned int fwidth = width; 540 | if (width > minwidth) { 541 | // we didn't fall-back so subtract the characters required for the exponent 542 | fwidth -= minwidth; 543 | } else { 544 | // not enough characters, so go back to default sizing 545 | fwidth = 0U; 546 | } 547 | if ((flags & FLAGS_LEFT) && minwidth) { 548 | // if we're padding on the right, DON'T pad the floating part 549 | fwidth = 0U; 550 | } 551 | 552 | // rescale the float value 553 | if (expval) { 554 | value /= conv.F; 555 | } 556 | 557 | // output the floating part 558 | const size_t start_idx = idx; 559 | idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); 560 | 561 | // output the exponent part 562 | if (minwidth) { 563 | // output the exponential symbol 564 | out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); 565 | // output the exponent value 566 | idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); 567 | // might need to right-pad spaces 568 | if (flags & FLAGS_LEFT) { 569 | while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); 570 | } 571 | } 572 | return idx; 573 | } 574 | #endif // PRINTF_SUPPORT_EXPONENTIAL 575 | #endif // PRINTF_SUPPORT_FLOAT 576 | 577 | 578 | // internal vsnprintf 579 | static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) 580 | { 581 | unsigned int flags, width, precision, n; 582 | size_t idx = 0U; 583 | 584 | if (!buffer) { 585 | // use null output function 586 | out = _out_null; 587 | } 588 | 589 | while (*format) 590 | { 591 | // format specifier? %[flags][width][.precision][length] 592 | if (*format != '%') { 593 | // no 594 | out(*format, buffer, idx++, maxlen); 595 | format++; 596 | continue; 597 | } 598 | else { 599 | // yes, evaluate it 600 | format++; 601 | } 602 | 603 | // evaluate flags 604 | flags = 0U; 605 | do { 606 | switch (*format) { 607 | case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; 608 | case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; 609 | case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; 610 | case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; 611 | case '#': flags |= FLAGS_HASH; format++; n = 1U; break; 612 | default : n = 0U; break; 613 | } 614 | } while (n); 615 | 616 | // evaluate width field 617 | width = 0U; 618 | if (_is_digit(*format)) { 619 | width = _atoi(&format); 620 | } 621 | else if (*format == '*') { 622 | const int w = va_arg(va, int); 623 | if (w < 0) { 624 | flags |= FLAGS_LEFT; // reverse padding 625 | width = (unsigned int)-w; 626 | } 627 | else { 628 | width = (unsigned int)w; 629 | } 630 | format++; 631 | } 632 | 633 | // evaluate precision field 634 | precision = 0U; 635 | if (*format == '.') { 636 | flags |= FLAGS_PRECISION; 637 | format++; 638 | if (_is_digit(*format)) { 639 | precision = _atoi(&format); 640 | } 641 | else if (*format == '*') { 642 | const int prec = (int)va_arg(va, int); 643 | precision = prec > 0 ? (unsigned int)prec : 0U; 644 | format++; 645 | } 646 | } 647 | 648 | // evaluate length field 649 | switch (*format) { 650 | case 'l' : 651 | flags |= FLAGS_LONG; 652 | format++; 653 | if (*format == 'l') { 654 | flags |= FLAGS_LONG_LONG; 655 | format++; 656 | } 657 | break; 658 | case 'h' : 659 | flags |= FLAGS_SHORT; 660 | format++; 661 | if (*format == 'h') { 662 | flags |= FLAGS_CHAR; 663 | format++; 664 | } 665 | break; 666 | #if defined(PRINTF_SUPPORT_PTRDIFF_T) 667 | case 't' : 668 | flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 669 | format++; 670 | break; 671 | #endif 672 | case 'j' : 673 | flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 674 | format++; 675 | break; 676 | case 'z' : 677 | flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 678 | format++; 679 | break; 680 | default : 681 | break; 682 | } 683 | 684 | // evaluate specifier 685 | switch (*format) { 686 | case 'd' : 687 | case 'i' : 688 | case 'u' : 689 | case 'x' : 690 | case 'X' : 691 | case 'o' : 692 | case 'b' : { 693 | // set the base 694 | unsigned int base; 695 | if (*format == 'x' || *format == 'X') { 696 | base = 16U; 697 | } 698 | else if (*format == 'o') { 699 | base = 8U; 700 | } 701 | else if (*format == 'b') { 702 | base = 2U; 703 | } 704 | else { 705 | base = 10U; 706 | flags &= ~FLAGS_HASH; // no hash for dec format 707 | } 708 | // uppercase 709 | if (*format == 'X') { 710 | flags |= FLAGS_UPPERCASE; 711 | } 712 | 713 | // no plus or space flag for u, x, X, o, b 714 | if ((*format != 'i') && (*format != 'd')) { 715 | flags &= ~(FLAGS_PLUS | FLAGS_SPACE); 716 | } 717 | 718 | // ignore '0' flag when precision is given 719 | if (flags & FLAGS_PRECISION) { 720 | flags &= ~FLAGS_ZEROPAD; 721 | } 722 | 723 | // convert the integer 724 | if ((*format == 'i') || (*format == 'd')) { 725 | // signed 726 | if (flags & FLAGS_LONG_LONG) { 727 | #if defined(PRINTF_SUPPORT_LONG_LONG) 728 | const long long value = va_arg(va, long long); 729 | idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); 730 | #endif 731 | } 732 | else if (flags & FLAGS_LONG) { 733 | const long value = va_arg(va, long); 734 | idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); 735 | } 736 | else { 737 | const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); 738 | idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); 739 | } 740 | } 741 | else { 742 | // unsigned 743 | if (flags & FLAGS_LONG_LONG) { 744 | #if defined(PRINTF_SUPPORT_LONG_LONG) 745 | idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); 746 | #endif 747 | } 748 | else if (flags & FLAGS_LONG) { 749 | idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); 750 | } 751 | else { 752 | const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); 753 | idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); 754 | } 755 | } 756 | format++; 757 | break; 758 | } 759 | #if defined(PRINTF_SUPPORT_FLOAT) 760 | case 'f' : 761 | case 'F' : 762 | if (*format == 'F') flags |= FLAGS_UPPERCASE; 763 | idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); 764 | format++; 765 | break; 766 | #if defined(PRINTF_SUPPORT_EXPONENTIAL) 767 | case 'e': 768 | case 'E': 769 | case 'g': 770 | case 'G': 771 | if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; 772 | if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; 773 | idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); 774 | format++; 775 | break; 776 | #endif // PRINTF_SUPPORT_EXPONENTIAL 777 | #endif // PRINTF_SUPPORT_FLOAT 778 | case 'c' : { 779 | unsigned int l = 1U; 780 | // pre padding 781 | if (!(flags & FLAGS_LEFT)) { 782 | while (l++ < width) { 783 | out(' ', buffer, idx++, maxlen); 784 | } 785 | } 786 | // char output 787 | out((char)va_arg(va, int), buffer, idx++, maxlen); 788 | // post padding 789 | if (flags & FLAGS_LEFT) { 790 | while (l++ < width) { 791 | out(' ', buffer, idx++, maxlen); 792 | } 793 | } 794 | format++; 795 | break; 796 | } 797 | 798 | case 's' : { 799 | const char* p = va_arg(va, char*); 800 | unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); 801 | // pre padding 802 | if (flags & FLAGS_PRECISION) { 803 | l = (l < precision ? l : precision); 804 | } 805 | if (!(flags & FLAGS_LEFT)) { 806 | while (l++ < width) { 807 | out(' ', buffer, idx++, maxlen); 808 | } 809 | } 810 | // string output 811 | while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { 812 | out(*(p++), buffer, idx++, maxlen); 813 | } 814 | // post padding 815 | if (flags & FLAGS_LEFT) { 816 | while (l++ < width) { 817 | out(' ', buffer, idx++, maxlen); 818 | } 819 | } 820 | format++; 821 | break; 822 | } 823 | 824 | case 'p' : { 825 | width = sizeof(void*) * 2U; 826 | flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; 827 | #if defined(PRINTF_SUPPORT_LONG_LONG) 828 | const bool is_ll = sizeof(uintptr_t) == sizeof(long long); 829 | if (is_ll) { 830 | idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); 831 | } 832 | else { 833 | #endif 834 | idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); 835 | #if defined(PRINTF_SUPPORT_LONG_LONG) 836 | } 837 | #endif 838 | format++; 839 | break; 840 | } 841 | 842 | case '%' : 843 | out('%', buffer, idx++, maxlen); 844 | format++; 845 | break; 846 | 847 | default : 848 | out(*format, buffer, idx++, maxlen); 849 | format++; 850 | break; 851 | } 852 | } 853 | 854 | // termination 855 | out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); 856 | 857 | // return written chars without terminating \0 858 | return (int)idx; 859 | } 860 | 861 | /////////////////////////////////////////////////////////////////////////////// 862 | 863 | int printf_(const char* format, ...) { 864 | //spinlock_lock(&io_lock); 865 | 866 | va_list va; 867 | va_start(va, format); 868 | char buffer[1]; 869 | const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); 870 | va_end(va); 871 | 872 | //spinlock_release(&io_lock); 873 | 874 | return ret; 875 | } 876 | 877 | int sprintf_(char* buffer, const char* format, ...) { 878 | va_list va; 879 | va_start(va, format); 880 | const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); 881 | va_end(va); 882 | return ret; 883 | } 884 | 885 | int snprintf_(char* buffer, size_t count, const char* format, ...) { 886 | va_list va; 887 | va_start(va, format); 888 | const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); 889 | va_end(va); 890 | return ret; 891 | } 892 | 893 | int vprintf_(const char* format, va_list va) { 894 | char buffer[1]; 895 | return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); 896 | } 897 | 898 | int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) { 899 | return _vsnprintf(_out_buffer, buffer, count, format, va); 900 | } 901 | 902 | int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) { 903 | va_list va; 904 | va_start(va, format); 905 | const out_fct_wrap_type out_fct_wrap = { out, arg }; 906 | const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); 907 | va_end(va); 908 | return ret; 909 | } --------------------------------------------------------------------------------