forked from PoliEcho-holding-org/kern
Added <errno.h> and made kernel use it
This commit is contained in:
+8
-10
@@ -1,14 +1,12 @@
|
||||
#include <bootloader.h>
|
||||
#include <kernel.h>
|
||||
#include <limine.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* This defines our expected Limine version
|
||||
*/
|
||||
// This defines our expected Limine version
|
||||
static volatile uint64_t lim_base_revision[] = LIMINE_BASE_REVISION(4);
|
||||
|
||||
struct bootloader bootloader = { 0 };
|
||||
|
||||
// Limine requests
|
||||
static struct limine_paging_mode_request lim_paging_req =
|
||||
{ .id = LIMINE_PAGING_MODE_REQUEST_ID, .revision = 4,
|
||||
.mode = LIMINE_PAGING_MODE_X86_64_4LVL, };
|
||||
@@ -28,6 +26,9 @@ static struct limine_framebuffer_request lim_fbuf_req =
|
||||
static struct limine_executable_file_request lim_exe_req =
|
||||
{ .id = LIMINE_EXECUTABLE_FILE_REQUEST_ID, .revision = 4 };
|
||||
|
||||
// Bootloader data struct
|
||||
struct bootloader bootloader = { 0 };
|
||||
|
||||
int boot_init(void) {
|
||||
if (LIMINE_BASE_REVISION_SUPPORTED(lim_base_revision) == false) panic("Unsupported Limine base revision.");
|
||||
|
||||
@@ -38,10 +39,7 @@ int boot_init(void) {
|
||||
struct limine_framebuffer_response* lim_fbuf_res = lim_fbuf_req.response;
|
||||
struct limine_executable_file_response* lim_exe_res = lim_exe_req.response;
|
||||
|
||||
if (lim_paging_res->mode != LIMINE_PAGING_MODE_X86_64_4LVL) {
|
||||
debugf("[boot] Expected 4 level paging.\n");
|
||||
return 1;
|
||||
}
|
||||
if (lim_paging_res->mode != LIMINE_PAGING_MODE_X86_64_4LVL) panic("Expected 4 level paging.");
|
||||
bootloader.hhdm_offset = lim_hhdm_res->offset;
|
||||
bootloader.kernel_phys_base = lim_kaddr_res->physical_base;
|
||||
bootloader.kernel_virt_base = lim_kaddr_res->virtual_base;
|
||||
@@ -58,6 +56,6 @@ int boot_init(void) {
|
||||
bootloader.fb_entries = lim_fbuf_res->framebuffers;
|
||||
bootloader.fb_entry_count = lim_fbuf_res->framebuffer_count;
|
||||
|
||||
return 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+2
-5
@@ -1,7 +1,6 @@
|
||||
#include "paging.h"
|
||||
#include "pmm.h"
|
||||
#include <kernel.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <gdt.h>
|
||||
|
||||
static struct gdt_entries gdt;
|
||||
@@ -105,8 +104,6 @@ int gdt_init(void) {
|
||||
memset(&tss, 0, sizeof(tss));
|
||||
tss_load(&tss);
|
||||
|
||||
debugf("[gdt] GDT loaded.\n");
|
||||
|
||||
return 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -1,5 +1,7 @@
|
||||
#include <kernel.h>
|
||||
#include <string.h>
|
||||
#include <system.h>
|
||||
#include <errno.h>
|
||||
#include <gdt.h>
|
||||
#include <idt.h>
|
||||
|
||||
@@ -7,25 +9,27 @@ __align(16) // performace
|
||||
static struct idt_entry idt[IDT_ENTRIES];
|
||||
static struct idt_pointer idtp;
|
||||
|
||||
void idt_set_gate(uint8_t i, void* handler, uint8_t attrib) {
|
||||
void idt_set_gate(uint8_t i, void* handler, uint8_t attrib, uint8_t ist) {
|
||||
size_t addr = (size_t)handler;
|
||||
idt[i].offset_low = (uint16_t)addr;
|
||||
idt[i].selector = GDT_KERNEL_CODE;
|
||||
idt[i].ist = 0;
|
||||
idt[i].__reserved = 0;
|
||||
idt[i].ist = ist & 7;
|
||||
idt[i].attrib = attrib;
|
||||
idt[i].offset_mid = (uint16_t)(addr >> 16);
|
||||
idt[i].offset_high = (uint32_t)(addr >> 32);
|
||||
idt[i].__reserved = 0;
|
||||
}
|
||||
|
||||
int idt_init(void) {
|
||||
memset(idt, 0, sizeof(idt));
|
||||
|
||||
idtp.size = sizeof(idt) - 1;
|
||||
idtp.offset = (size_t)&idt;
|
||||
idtp.offset = (uintptr_t)&idt;
|
||||
|
||||
// Load IDT instruction
|
||||
cli();
|
||||
__asm__ volatile ("lidt %0" : : "m"(idtp));
|
||||
|
||||
debugf("[idt] IDT initalized.\n");
|
||||
return 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -2,6 +2,7 @@
|
||||
#include <serial.h>
|
||||
#include <string.h>
|
||||
#include <system.h>
|
||||
#include <errno.h>
|
||||
#include <idt.h>
|
||||
#include <isr.h>
|
||||
|
||||
@@ -64,24 +65,23 @@ void pic_remap(void) {
|
||||
int isr_init(void) {
|
||||
memset(irq_handlers, 0, sizeof(irq_handlers));
|
||||
|
||||
if (idt_init() != 0) {
|
||||
debugf("[isr] Could not load IDT.");
|
||||
return 1;
|
||||
}
|
||||
idt_init();
|
||||
|
||||
for (int i = 0; i < 48; i++) {
|
||||
// First 32 IRSs are exceptions (traps) and the next 16 are hardware interrupts
|
||||
idt_set_gate(i, isr_redirect_table[i], IDT_FLAG_INT);
|
||||
idt_set_gate(i, isr_redirect_table[i], i < 32 ? IDT_FLAG_TRAP : IDT_FLAG_INT, 0);
|
||||
}
|
||||
idt_set_gate(0x03, isr_redirect_table[3], IDT_FLAG_USER); // Allow breakpoints
|
||||
idt_set_gate(0x80, isr128, IDT_FLAG_USER); // Syscall
|
||||
|
||||
idt_set_gate(0x02, isr_redirect_table[2], IDT_FLAG_TRAP, 2); // Use IST2 for NMIs
|
||||
idt_set_gate(0x03, isr_redirect_table[3], IDT_FLAG_USER, 0); // Allow breakpoints
|
||||
idt_set_gate(0x08, isr_redirect_table[8], IDT_FLAG_TRAP, 1); // Use IST1 for Double Faults
|
||||
idt_set_gate(0x80, isr128, IDT_FLAG_USER, 0); // Syscall
|
||||
|
||||
pic_remap();
|
||||
// TODO: apic_init();
|
||||
|
||||
sti();
|
||||
debugf("[isr] ISR initialized.\n");
|
||||
return 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void isr_register(uint8_t i, isr_handler_ptr handler) {
|
||||
|
||||
+6
-7
@@ -1,5 +1,4 @@
|
||||
#include <bootloader.h>
|
||||
#include <kmalloc.h>
|
||||
#include <paging.h>
|
||||
#include <serial.h>
|
||||
#include <splash.h>
|
||||
@@ -13,10 +12,10 @@
|
||||
__noreturn
|
||||
void _start(void) {
|
||||
// Serial console for debugging
|
||||
if (serial_init(COM1) != 0) panic("Failed to initialize Serial driver.");
|
||||
if (serial_init(COM1) < 0) panic("Failed to initialize Serial driver.");
|
||||
|
||||
// Load structures from bootloader
|
||||
if (boot_init() != 0) panic("Failed to parse bootloader structures.");
|
||||
if (boot_init() < 0) panic("Failed to parse bootloader structures.");
|
||||
|
||||
// The most important function in the world.
|
||||
splash();
|
||||
@@ -28,12 +27,12 @@ void _start(void) {
|
||||
debugf("[kernel] CPUID_GETVENDORID: %s\n", s);
|
||||
|
||||
// System init
|
||||
if (gdt_init() != 0) panic("Failed to initalize GDT.");
|
||||
if (isr_init() != 0) panic("Failed to initalize ISRs.");
|
||||
if (gdt_init() < 0) panic("Failed to initalize GDT.");
|
||||
if (isr_init() < 0) panic("Failed to initalize ISRs.");
|
||||
|
||||
// Memory management
|
||||
if (pmm_init() != 0) panic("Failed to initalize Physical Memory Manager.");
|
||||
if (pag_init() != 0) panic("Failed to initalize Paging.");
|
||||
if (pmm_init() < 0) panic("Failed to initalize Physical Memory Manager.");
|
||||
if (pag_init() < 0) panic("Failed to initalize Paging.");
|
||||
|
||||
// Patiently wait for things to do :)
|
||||
while (1);
|
||||
|
||||
+55
-66
@@ -3,6 +3,7 @@
|
||||
#include <limine.h>
|
||||
#include <string.h>
|
||||
#include <serial.h>
|
||||
#include <errno.h>
|
||||
#include <pmm.h>
|
||||
|
||||
#include <paging.h>
|
||||
@@ -12,19 +13,17 @@ uint64_t* pag_kernel_pml4 = NULL;
|
||||
|
||||
int pag_init(void) {
|
||||
pag_kernel_pml4 = TO_VIRT(pmm_alloc());
|
||||
if (pag_kernel_pml4 == NULL) {
|
||||
debugf("[pag] Failed to allocate kernel PML4. Not enough memory.\n");
|
||||
return 1;
|
||||
}
|
||||
if (IS_ERR(pag_kernel_pml4)) return PTR_ERR(pag_kernel_pml4);
|
||||
memset(pag_kernel_pml4, 0, PAGE_SIZE);
|
||||
|
||||
pag_map_region(
|
||||
int ret = pag_map_region(
|
||||
pag_kernel_pml4,
|
||||
(void*)bootloader.kernel_virt_base,
|
||||
(void*)bootloader.kernel_phys_base,
|
||||
dceil(bootloader.executable->size, PAGE_SIZE),
|
||||
PTE_FLAG_WRITE | PTE_FLAG_PRESENT
|
||||
);
|
||||
if (ret != SUCCESS) return ret;
|
||||
|
||||
for (size_t i = 0; i < bootloader.mm_entry_count; i++) {
|
||||
struct limine_memmap_entry* entry = bootloader.mm_entries[i];
|
||||
@@ -48,115 +47,106 @@ int pag_init(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pag_map_region(
|
||||
ret = pag_map_region(
|
||||
pag_kernel_pml4,
|
||||
(void*)(entry->base + bootloader.hhdm_offset), (void*)entry->base,
|
||||
dceil(entry->length, PAGE_SIZE),
|
||||
flags | PTE_FLAG_PRESENT
|
||||
);
|
||||
if (ret != SUCCESS) return ret;
|
||||
}
|
||||
|
||||
debugf("[pag] Switching to kernel PML4 at %p.\n", pag_kernel_pml4);
|
||||
pag_set_pml4(pag_kernel_pml4);
|
||||
|
||||
debugf("[pag] Paging initialized.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t* pag_get_pte(uint64_t* pml4, void* vaddr) {
|
||||
uintptr_t v = (uintptr_t)vaddr;
|
||||
if (v % PAGE_SIZE != 0) {
|
||||
debugf("[pag] Could not get PTE from non-aligned virtual address %p.\n", vaddr);
|
||||
return NULL;
|
||||
}
|
||||
if (v % PAGE_SIZE != 0) return ERR_PTR(-EALIGN);
|
||||
|
||||
uint64_t* pdpt = pag_get_next_tab(pml4, PML4_IDX(v));
|
||||
if (pdpt == NULL) {
|
||||
debugf("[pag] Could not get PTE. %p was not found in PML4.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pdpt)) return pdpt;
|
||||
|
||||
uint64_t* pd = pag_get_next_tab(pdpt, PDPT_IDX(v));
|
||||
if (pd == NULL) {
|
||||
debugf("[pag] Could not get PTE. %p was not found in PDPT.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pd)) return pd;
|
||||
|
||||
uint64_t* pt = pag_get_next_tab(pd, PD_IDX(v));
|
||||
if (pt == NULL) {
|
||||
debugf("[pag] Could not get PTE. %p was not found in PD.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pt)) return pt;
|
||||
|
||||
return &pt[PT_IDX(v)];
|
||||
}
|
||||
|
||||
uint64_t* pag_new_pte(uint64_t* pml4, void* vaddr) {
|
||||
uintptr_t v = (uintptr_t)vaddr;
|
||||
if (v % PAGE_SIZE != 0) {
|
||||
debugf("[pag] Could not create PTE for non-aligned virtual address %p.\n", vaddr);
|
||||
return NULL;
|
||||
}
|
||||
if (v % PAGE_SIZE != 0) return ERR_PTR(-EALIGN);
|
||||
|
||||
uint64_t* pdpt = pag_new_next_tab(pml4, PML4_IDX(v));
|
||||
if (pdpt == NULL) {
|
||||
debugf("[pag] Failed to create PDPT. Not enough memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pdpt)) return pdpt;
|
||||
|
||||
uint64_t* pd = pag_new_next_tab(pdpt, PDPT_IDX(v));
|
||||
if (pd == NULL) {
|
||||
debugf("[pag] Failed to create PD. Not enough memory.\n");
|
||||
if (pdpt != NULL) pmm_free(pdpt);
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pd)) return pd;
|
||||
|
||||
uint64_t* pt = pag_new_next_tab(pd, PD_IDX(v));
|
||||
if (pt == NULL) {
|
||||
debugf("[pag] Failed to create PT. Not enough memory.\n");
|
||||
if (pd != NULL) pmm_free(pd);
|
||||
if (pdpt != NULL) pmm_free(pdpt);
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(pt)) return pt;
|
||||
|
||||
return &pt[PT_IDX(v)];
|
||||
}
|
||||
|
||||
void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags) {
|
||||
int pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags) {
|
||||
uintptr_t p = (uintptr_t)paddr;
|
||||
if (p % PAGE_SIZE != 0) {
|
||||
debugf("[pag] Could not map %p to %p. Physical address is not aligned.\n", paddr, vaddr);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t* page = pag_new_pte(pml4, vaddr);
|
||||
*page = PTE_ADDR(p) | flags | PTE_FLAG_PRESENT;
|
||||
if (p % PAGE_SIZE != 0) return -EALIGN;
|
||||
|
||||
uint64_t* pte = pag_new_pte(pml4, vaddr);
|
||||
if (IS_ERR(pte)) return PTR_ERR(pte);
|
||||
|
||||
*pte = PTE_ADDR(p) | flags | PTE_FLAG_PRESENT;
|
||||
pag_invalidate(vaddr);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags) {
|
||||
int pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
pag_map(pml4, vaddr + i * PAGE_SIZE, paddr + i * PAGE_SIZE, flags);
|
||||
int ret = pag_map(pml4, vaddr + i * PAGE_SIZE, paddr + i * PAGE_SIZE, flags);
|
||||
if (ret != SUCCESS) return ret;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// TODO: This is a pmm memory leak. If all pages in a PT are unmapped, remove
|
||||
// that PT from it's PD. This applies to PDs & PDPTs too.
|
||||
void pag_unmap(uint64_t* pml4, void* vaddr) {
|
||||
int pag_unmap(uint64_t* pml4, void* vaddr) {
|
||||
uint64_t* pte = pag_get_pte(pml4, vaddr);
|
||||
if (IS_ERR(pte)) return PTR_ERR(pte);
|
||||
|
||||
*pte = 0;
|
||||
pag_invalidate(vaddr);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count) {
|
||||
int pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
pag_unmap(pml4, vaddr + i * PAGE_SIZE);
|
||||
int ret = pag_unmap(pml4, vaddr + i * PAGE_SIZE);
|
||||
if (ret != SUCCESS) return ret;
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags) {
|
||||
int pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags) {
|
||||
uint64_t* pte = pag_get_pte(pml4, vaddr);
|
||||
if (IS_ERR(pte)) return PTR_ERR(pte);
|
||||
|
||||
*pte = PTE_ADDR(*pte) | flags | PTE_FLAG_PRESENT;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void* pag_get_phys(uint64_t* pml4, void* vaddr) {
|
||||
uint64_t* pte = pag_get_pte(pml4, vaddr);
|
||||
if (IS_ERR(pte)) return pte;
|
||||
|
||||
return (void*)PTE_ADDR(*pte);
|
||||
}
|
||||
|
||||
@@ -171,21 +161,20 @@ uint64_t* pag_get_pml4(void) {
|
||||
}
|
||||
|
||||
uint64_t* pag_get_next_tab(uint64_t* tab, offset_t index) {
|
||||
if (index >= 512 || tab == NULL) return NULL;
|
||||
else if (tab[index] & PTE_FLAG_PRESENT) return TO_VIRT(tab[index] & PTE_ADDR_MASK);
|
||||
else return NULL;
|
||||
if (tab == NULL || IS_ERR(tab)) return ERR_PTR(-EFAULT);
|
||||
if (index >= 512) return ERR_PTR(-EINVAL);
|
||||
|
||||
if (tab[index] & PTE_FLAG_PRESENT) return TO_VIRT(PTE_ADDR(tab[index]));
|
||||
else return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
uint64_t* pag_new_next_tab(uint64_t* tab, offset_t index) {
|
||||
if (tab == NULL) return NULL;
|
||||
uint64_t* vaddr = pag_get_next_tab(tab, index);
|
||||
if (vaddr != NULL) return vaddr;
|
||||
if (!IS_ERR(vaddr)) return vaddr;
|
||||
if (PTR_ERR(vaddr) != -ENOENT) return vaddr;
|
||||
|
||||
void* paddr = pmm_alloc();
|
||||
if (!paddr) {
|
||||
debugf("[pag] Failed to allocate page for new table.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (IS_ERR(paddr)) return paddr;
|
||||
|
||||
vaddr = TO_VIRT(paddr);
|
||||
memset(vaddr, 0, PAGE_SIZE);
|
||||
|
||||
+13
-21
@@ -3,6 +3,7 @@
|
||||
#include <limine.h>
|
||||
#include <paging.h>
|
||||
#include <serial.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <pmm.h>
|
||||
|
||||
@@ -10,7 +11,6 @@ uint64_t* pmm_head = NULL;
|
||||
size_t pmm_total_mem = 0;
|
||||
|
||||
int pmm_init(void) {
|
||||
|
||||
debugf("[pmm] Physical memory map:\n"); // We're gonna use the loop to also print the memmap
|
||||
for (size_t i = 0; i < bootloader.mm_entry_count; i++) {
|
||||
struct limine_memmap_entry* entry = bootloader.mm_entries[i];
|
||||
@@ -39,37 +39,29 @@ int pmm_init(void) {
|
||||
}
|
||||
|
||||
debugf("[pmm] %d bytes of available memory.\n", pmm_total_mem);
|
||||
if (pmm_total_mem == 0) return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void* pmm_alloc() {
|
||||
if (pmm_head == NULL) {
|
||||
debugf("[pmm] Could not allocate page, not enough memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
if (pmm_head == NULL) return ERR_PTR(-ENOMEM);
|
||||
|
||||
uint64_t* addr = pmm_head;
|
||||
if ((uintptr_t)addr % PAGE_SIZE != 0) {
|
||||
debugf("[pmm] Tried to allocate non-aligned page.\n");
|
||||
// It may fix itself if a page gets freed
|
||||
// panic("pmm_head corrupted.");
|
||||
return NULL;
|
||||
}
|
||||
if ((uintptr_t)addr % PAGE_SIZE != 0) panic("pmm_head corrupted.");
|
||||
pmm_head = (uint64_t*)(*addr);
|
||||
|
||||
return TO_PHYS(addr);
|
||||
}
|
||||
|
||||
void pmm_free(void* page) {
|
||||
if (page == NULL) {
|
||||
debugf("[pmm] Tried to free NULL.\n");
|
||||
return;
|
||||
}
|
||||
if ((uintptr_t)page % PAGE_SIZE != 0) {
|
||||
debugf("[pmm] Tried to free non-aligned page.\n");
|
||||
return;
|
||||
}
|
||||
int pmm_free(void* page) {
|
||||
if (page == NULL || IS_ERR(page)) return -EFAULT;
|
||||
if ((uintptr_t)page % PAGE_SIZE != 0) return -EALIGN;
|
||||
|
||||
uint64_t* addr = TO_VIRT(page);
|
||||
*addr = (uint64_t)pmm_head;
|
||||
pmm_head = addr;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -1,3 +1,4 @@
|
||||
#include "errno.h"
|
||||
#include <vsnprintf.h>
|
||||
#include <kernel.h>
|
||||
#include <system.h>
|
||||
@@ -11,13 +12,15 @@ int serial_init(int dev) {
|
||||
outb(dev + 3, 0x03); // 8 bits, no parity, one stop bit
|
||||
outb(dev + 2, 0xC7); // Enable FIFO, clear them, with 14 byte threshold
|
||||
outb(dev + 4, 0x0B); // IRQs enabled, RTS/DSR set
|
||||
|
||||
outb(dev + 4, 0x1E); // Set in loopback mode, test the serial chip
|
||||
outb(dev + 0, 0xAE); // Test serial chip (send byte 0xAE and check if it returns)
|
||||
|
||||
if (inb(dev + 0) != 0xAE) return 1;
|
||||
if (inb(dev + 0) != 0xAE) return -ENODEV;
|
||||
|
||||
outb(dev + 4, 0x0F); // non-loopback, IRQs enabled, OUT#1 and OUT#2 bits enabled
|
||||
|
||||
debugf("\x1b[H\x1b[JSerial display initiated!\n\n");
|
||||
serial_cls(COM1);
|
||||
serial_puts(COM1, "Serial display (COM1)\n\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -54,6 +57,10 @@ void serial_puts(int dev, char* s) {
|
||||
serial_write(dev, s, len);
|
||||
}
|
||||
|
||||
void serial_cls(int dev) {
|
||||
serial_write(dev, "\x1b[H\x1b[J", 6);
|
||||
}
|
||||
|
||||
int debugf(char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
Reference in New Issue
Block a user