kernel: Implemented various features

+ lapic: Start implementation
+ ioapic: Start implementation
+ apic: Now properly working
+ madt: Start implementation
+ pit: Start implementation
+ smp: Start implementation (only grabs the bootstrap processor's LAPIC ID)
This commit is contained in:
RaphProductions 2025-05-20 08:29:23 +02:00
parent dcea7360d2
commit b2cf9b4710
22 changed files with 520 additions and 79 deletions

View file

@ -12,10 +12,41 @@
#include <boot/limine.h>
#include <lib/log.h>
#include <mm/memop.h>
#include <mm/pmm.h>
#include <stdint.h>
static bool __acpi_use_xsdt;
static uint64_t __acpi_rsdt_addr;
void *acpi_find_table(char *sign) {
if (__acpi_use_xsdt) {
acpi_xsdt_t *xsdt = (acpi_xsdt_t*)__acpi_rsdt_addr;
uint32_t entries = (xsdt->hdr.len - sizeof(xsdt->hdr)) / 8;
for (uint32_t i = 0; i < entries; i++)
{
acpi_sdt_hdr_t *h = (acpi_sdt_hdr_t *)higher_half(*((uint64_t*)xsdt->entries + i));
if (!memcmp(h->sign, sign, 4))
return (void *) h;
}
return NULL;
}
acpi_rsdt_t *rsdt = (acpi_rsdt_t*)__acpi_rsdt_addr;
int entries = (rsdt->hdr.len - sizeof(rsdt->hdr)) / 4;
uint32_t *entries_hhalf = (uint32_t *)higher_half((uint64_t)rsdt->entries);
for (int i = 0; i < entries; i++)
{
acpi_sdt_hdr_t *h = (acpi_sdt_hdr_t *)(entries_hhalf[i] + (i * sizeof(uint32_t)));
if (!memcmp(h->sign, sign, 4))
return (void *) h;
}
return NULL;
}
void acpi_init() {
acpi_rsdp_t *rsdp = (acpi_rsdp_t*)limine_get_rsdp();
if (memcmp(rsdp->sign, "RSD PTR ", 8))
@ -30,13 +61,13 @@ void acpi_init() {
acpi_xsdp_t *xsdp = (acpi_xsdp_t *)rsdp;
__acpi_use_xsdt = 1;
__acpi_rsdt_addr = xsdp->xsdt_addr;
__acpi_rsdt_addr = higher_half(xsdp->xsdt_addr);
goto initialized;
}
__acpi_use_xsdt = 0;
__acpi_rsdt_addr = rsdp->rsdt_addr; // Do not use a pointer, to shut up the compiler.
__acpi_rsdt_addr = higher_half(rsdp->rsdt_addr); // Do not use a pointer, to shut up the compiler.
initialized:
trace("acpi: Initialized!\n");

View file

@ -15,14 +15,36 @@ typedef struct {
char oemid[6];
char rev;
uint32_t rsdt_addr;
} acpi_rsdp_t;
} __attribute__((packed)) acpi_rsdp_t;
typedef struct {
acpi_rsdp_t base;
uint32_t len;
uint64_t xsdt_addr;
char chksumex;
} acpi_xsdp_t;
} __attribute__((packed)) acpi_xsdp_t;
void acpi_init();
void acpi_init();
typedef struct {
char sign[4];
uint32_t len;
uint8_t rev;
uint8_t chksum;
char oemid[6];
char oemtabid[8];
uint32_t oemrev;
uint32_t creaid;
uint32_t crearev;
} __attribute__((packed)) acpi_sdt_hdr_t;
typedef struct {
acpi_sdt_hdr_t hdr;
char entries[];
} acpi_rsdt_t;
typedef struct {
acpi_sdt_hdr_t hdr;
char entries[];
} acpi_xsdt_t;
void *acpi_find_table(char *sign);
void acpi_init();

42
kernel/src/acpi/madt.c Normal file
View file

@ -0,0 +1,42 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* madt.c - MADT lookup
*/
#include "acpi/acpi.h"
#include "lib/log.h"
#include <acpi/madt.h>
acpi_madt_ioapic_t* madt_ioapic_list[128];
acpi_madt_iso_t* madt_iso_list[128];
uint32_t madt_ioapic_len = 0;
uint32_t madt_iso_len = 0;
uint64_t *madt_lapic_addr = (uint64_t *)0;
void madt_init() {
void *addr = acpi_find_table("APIC");
acpi_madt_t *madt = (acpi_madt_t *)addr;
uint64_t offset = 0;
while (1) {
if (offset > madt->hdr.len - sizeof(acpi_madt_t))
break;
madt_entry *entry = (madt_entry *)(madt->table + offset);
if (entry->type == 1)
madt_ioapic_list[madt_ioapic_len++] = (acpi_madt_ioapic_t *)entry;
else if (entry->type == 2)
madt_iso_list[madt_iso_len++] = (acpi_madt_iso_t *)entry;
else if (entry->type == 5)
madt_lapic_addr = (uint64_t *)((acpi_madt_lapic_addr_override_t *)entry)->plapic;
offset += entry->len;
}
trace("madt: Initialized\n");
}

61
kernel/src/acpi/madt.h Normal file
View file

@ -0,0 +1,61 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* madt.h - MADT lookup
*/
#pragma once
#include <acpi/acpi.h>
#include <stdint.h>
typedef struct {
acpi_sdt_hdr_t hdr;
uint32_t lapic_addr;
uint32_t flags;
char table[];
} acpi_madt_t;
typedef struct {
uint8_t type;
uint8_t len;
} madt_entry;
typedef struct {
madt_entry entry;
uint8_t cpu_id;
uint8_t lapic_id;
uint32_t flags;
} acpi_madt_lapic_t;
typedef struct {
madt_entry entry;
uint8_t ioapic_id;
uint8_t reserved;
uint32_t ioapic_addr;
uint32_t gsi_base;
} acpi_madt_ioapic_t;
typedef struct {
madt_entry entry;
uint8_t bus;
uint8_t source;
uint32_t gsi;
uint16_t flags;
} acpi_madt_iso_t; // iso = interrupt source override
typedef struct {
madt_entry entry;
uint16_t reserved;
uint64_t plapic;
} acpi_madt_lapic_addr_override_t;
extern acpi_madt_ioapic_t* madt_ioapic_list[128];
extern acpi_madt_iso_t* madt_iso_list[128];
extern uint32_t madt_ioapic_len;
extern uint32_t madt_iso_len;
extern uint64_t *lapic_addr;
void madt_init();

View file

@ -18,5 +18,12 @@ void cpu_load_pm(pagemap_t pm);
// Invalidate a page table entry
void cpu_invalidate_page(uint64_t vaddr);
// Initialize SMP. The implementations are in arch since the Limine
// structure changes depending on the architecture.
void cpu_init_smp();
// Initialize the CPU's timer
void cpu_init_timer();
// Disable interrupts and halt the system.
void hcf();

View file

View file

@ -37,11 +37,9 @@
%macro isr_err_stub 1
isr_stub_%+%1:
push %1 ; push intno into the stack
push %1
pushall
mov rdi, rsp ; put the stack as the first arg.
mov rdi, rsp
call idt_interrupt_handler
@ -102,10 +100,17 @@ isr_no_err_stub 29
isr_err_stub 30
isr_no_err_stub 31
%assign i 32
%rep 224
isr_no_err_stub i
%assign i i+1
%endrep
global isr_stub_table
isr_stub_table:
%assign i 0
%rep 32
%rep 256
dq isr_stub_%+i
%assign i i+1
%endrep

View file

@ -4,65 +4,38 @@
*
* idt.c - x86_64 Interrupt Descriptor Table implementation.
*/
#include "dev/lapic.h"
#if defined(__x86_64__)
#include "arch/cpu.h"
#include "lib/log.h"
#include <stdint.h>
#include <arch/cpu.h>
#include <arch/x86_64/idt.h>
#include <arch/x86_64/smp.h>
#include <dev/ioapic.h>
#include <lib/log.h>
__attribute__((aligned(0x10)))
static idt_entry_t idt[256];
static idtr_t idtr;
static void __panic_display_bt(registers_t *regs) {
if (regs->cs == 0x43 || regs->cs == 0x3B) {
fatal("The backtrace can't be dumped from a userspace process.\n");
return; // Don't try to backtrace userspace
}
static uint8_t __idt_vectors[256];
static interrupt_handler __idt_external_handlers[256];
fatal("-- BACKTRACE --\n");
void idt_register_handler(uint8_t vector, void *isr) {
if (vector <= 16)
ioapic_redir_irq(bootstrap_lapic_id, vector + 32, vector, false);
// First print the current instruction pointer from the interrupt frame
if (regs->rip) {
fatal("* %p (current)\n", regs->rip);
}
uint64_t *frame = (uint64_t*)regs->rbp;
if (!frame || (uint64_t)frame < 0xffffffff80000000) {
fatal("No further stack frames available\n");
return;
}
// Frame format in x86_64:
// [rbp] -> previous rbp
// [rbp+8] -> return address
int depth = 0;
while (frame && depth < 16) { // Limit depth to avoid infinite loops
// Validate both frame and return address pointers
uint64_t *ret_addr_ptr = frame + 1;
if ((uint64_t)ret_addr_ptr < 0xffffffff80000000) {
break;
}
uint64_t ret_addr = *ret_addr_ptr;
if (ret_addr < 0xffffffff80000000 || ret_addr > 0xfffffffffffff000) {
break;
}
fatal("* %p\n", ret_addr);
uint64_t next_rbp = *frame;
if (next_rbp < 0xffffffff80000000 || next_rbp > 0xfffffffffffff000) {
break;
}
frame = (uint64_t*)next_rbp;
depth++;
}
fatal("\n");
__idt_external_handlers[vector] = isr;
__idt_vectors[vector <= 16 ? vector + 32 : vector] = vector <= 16 ? VT_HWI : VT_SWI;
}
void idt_interrupt_handler(registers_t *regs) {
if (__idt_vectors[regs->int_no] == VT_SPURIOUS || __idt_vectors[regs->int_no] == VT_NONE)
return;
if (regs->int_no < 32) {
fatal("Kernel panic: CPU exception %d\n", regs->int_no);
fatal("rax: %p, rbx: %p, rbp: %p, rdx\n", regs->rax, regs->rbx, regs->rbp, regs->rdx);
fatal("rdi: %p, rsi: %p, rcx: %p\n", regs->rdi, regs->rsi, regs->rcx);
@ -71,39 +44,65 @@ void idt_interrupt_handler(registers_t *regs) {
fatal("r14: %p, r15: %p\n", regs->r14, regs->r15);
fatal("rip: %p, cs: %p, ss: %p\n", regs->rip, regs->cs, regs->ss);
fatal("rflags: %p, err: %d, rsp: %p\n", regs->rflags, regs->err_code, regs->rsp);
__panic_display_bt(regs);
hcf();
}
int vec = regs->int_no;
if (vec >= 32 && vec < 48)
vec -= 32;
if (__idt_vectors[regs->int_no] == VT_HWI) {
interrupt_handler i = __idt_external_handlers[vec];
i(regs);
} else if (__idt_vectors[regs->int_no] == VT_SWI) {
interrupt_handler i = __idt_external_handlers[regs->int_no];
i(regs);
}
lapic_eoi();
}
void idt_set_descriptor(uint8_t vector, void* isr, uint8_t flags) {
idt_entry_t* descriptor = &idt[vector];
idt_entry_t* descriptor = &idt[vector];
descriptor->isr_low = (uint64_t)isr & 0xFFFF;
descriptor->kernel_cs = 0x08;
descriptor->ist = 0;
descriptor->attributes = flags;
descriptor->isr_mid = ((uint64_t)isr >> 16) & 0xFFFF;
descriptor->isr_high = ((uint64_t)isr >> 32) & 0xFFFFFFFF;
descriptor->reserved = 0;
descriptor->isr_low = (uint64_t)isr & 0xFFFF;
descriptor->kernel_cs = 0x08;
descriptor->ist = 0;
descriptor->attributes = flags;
descriptor->isr_mid = ((uint64_t)isr >> 16) & 0xFFFF;
descriptor->isr_high = ((uint64_t)isr >> 32) & 0xFFFFFFFF;
descriptor->reserved = 0;
}
static bool vectors[32];
extern void* isr_stub_table[];
void idt_init() {
idtr.base = (uintptr_t)&idt[0];
idtr.limit = (uint16_t)sizeof(idt_entry_t) * 32 - 1;
idtr.base = (uintptr_t)&idt[0];
idtr.limit = (uint16_t)sizeof(idt_entry_t) * 256 - 1;
for (uint8_t vector = 0; vector < 32; vector++) {
idt_set_descriptor(vector, isr_stub_table[vector], 0x8E);
vectors[vector] = true;
}
for (uint8_t vector = 0; vector < 32; vector++) {
idt_set_descriptor(vector, isr_stub_table[vector], 0x8E);
__idt_vectors[vector] = VT_EXCEPTION;
}
trace("idt: Exception vectors has been set!\n");
__asm__ volatile ("lidt %0" : : "m"(idtr));
__asm__ volatile ("sti");
for (uint8_t vector = 32; vector < 48; vector++) {
idt_set_descriptor(vector, isr_stub_table[vector], 0x8E);
__idt_vectors[vector] = VT_HWI;
}
trace("idt: Hardware interrupt vectors has been set!\n");
for (uint16_t vector = 48; vector < 256; vector++) {
idt_set_descriptor(vector, isr_stub_table[vector], 0x8E);
__idt_vectors[vector] = VT_NONE;
}
__idt_vectors[IDT_SPURIOUS_INT] = VT_SPURIOUS;
trace("idt: Spurious interrupt vector has been set!\n");
trace("arch: IDT loaded successfully\n");
__asm__ volatile ("lidt %0" : : "m"(idtr));
__asm__ volatile ("sti");
trace("arch: IDT loaded successfully\n");
}
#endif

View file

@ -8,6 +8,14 @@
#include <stdint.h>
#define VT_NONE 0
#define VT_EXCEPTION 1
#define VT_HWI 2
#define VT_SWI 3
#define VT_SPURIOUS 4
#define IDT_SPURIOUS_INT 0xFF
typedef struct {
uint64_t r15;
uint64_t r14;
@ -33,6 +41,8 @@ typedef struct {
uint64_t ss;
} __attribute__((packed)) registers_t;
typedef void(*interrupt_handler)(registers_t*);
typedef struct {
uint16_t isr_low;
uint16_t kernel_cs;
@ -48,4 +58,5 @@ typedef struct {
uint64_t base;
} __attribute__((packed)) idtr_t;
void idt_register_handler(uint8_t vector, void *isr);
void idt_init(void);

View file

@ -0,0 +1,23 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* io.h - x86_64 I/O functions
*/
#pragma once
#include <stdint.h>
inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) : "memory");
}
inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ( "inb %w1, %b0"
: "=a"(ret)
: "Nd"(port)
: "memory");
return ret;
}

View file

@ -0,0 +1,30 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* pit.c - x86_64 Programmable Interval Timer implementation.
*/
#if defined(__x86_64__)
#include <arch/x86_64/io.h>
#include <arch/x86_64/idt.h>
#include <lib/log.h>
void pit_handler(registers_t *reg) {
trace("pit: Interrupt!\n");
}
void cpu_init_timer() {
outb(0x43, 0x36);
uint16_t div = (uint16_t)(1193180 / 1000);
// Since the PIT uses 8-bit IO operations,
// we need to split div in 2.
outb(0x40, (uint8_t)div);
outb(0x40, (uint8_t)(div >> 8));
idt_register_handler(0, pit_handler);
}
#endif

View file

@ -0,0 +1,17 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* smp.c - x86_64 Symetric Multiprocessing implementation
*/
#include <boot/limine.h>
#include <deps/limine.h>
#include <stdint.h>
uint32_t bootstrap_lapic_id;
void cpu_init_smp() {
struct limine_mp_response *smp = limine_get_smp();
bootstrap_lapic_id = smp->bsp_lapic_id;
}

View file

@ -0,0 +1,12 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* smp.c - x86_64 Symetric Multiprocessing definitions
*/
#pragma once
#include <stdint.h>
extern uint32_t bootstrap_lapic_id;

View file

@ -69,6 +69,12 @@ static volatile struct limine_rsdp_request rsdp_req = {
.revision = 0
};
__attribute__((used, section(".limine_requests")))
static volatile struct limine_mp_request smp_req = {
.id = LIMINE_MP_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests_start")))
static volatile LIMINE_REQUESTS_START_MARKER;
@ -117,3 +123,4 @@ uint64_t limine_get_kernel_vaddr() { return kaddr_req.response->virtual_base; }
uint64_t limine_get_kernel_paddr() { return kaddr_req.response->physical_base; }
uint64_t limine_get_kernel_ehdr_addr() { return (uint64_t)execfile_req.response->executable_file->address; }
uint64_t limine_get_rsdp() { return rsdp_req.response->address + limine_get_hhdm_offset(); }
struct limine_mp_response *limine_get_smp() { return smp_req.response; }

View file

@ -36,3 +36,4 @@ uint64_t limine_get_kernel_vaddr();
uint64_t limine_get_kernel_paddr();
uint64_t limine_get_kernel_ehdr_addr();
uint64_t limine_get_rsdp();
struct limine_mp_response *limine_get_smp();

87
kernel/src/dev/ioapic.c Normal file
View file

@ -0,0 +1,87 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* ioapic.c - I/O APIC implementation.
*/
#include <stdint.h>
#include <stddef.h>
#include <acpi/madt.h>
#include <dev/ioapic.h>
#include <lib/log.h>
#include <mm/pmm.h>
static void __ioapic_write(acpi_madt_ioapic_t *ioapic, uint32_t reg, uint32_t val) {
*(volatile uint32_t *)(higher_half(ioapic->ioapic_addr)) = reg;
*(volatile uint32_t *)(higher_half(ioapic->ioapic_addr+0x10)) = val;
}
static uint32_t __ioapic_read(acpi_madt_ioapic_t *ioapic, uint32_t reg) {
*(volatile uint32_t *)(higher_half(ioapic->ioapic_addr)) = reg;
return *(volatile uint32_t *)(higher_half(ioapic->ioapic_addr+0x10));
}
void ioapic_init() {
acpi_madt_ioapic_t *ioapic = madt_ioapic_list[0];
uint32_t value = __ioapic_read(ioapic, IOAPIC_VERSION);
uint32_t count = (value >> 16) & 0xFF;
for (uint8_t i = 0; i <= count; ++i) {
__ioapic_write(ioapic,
IOAPIC_REDTBL+2 * i,
0x00010000 | (32 + i));
__ioapic_write(ioapic,
IOAPIC_REDTBL + 2 * i + 1, 0);
}
trace("ioapic: Initialized\n");
}
uint32_t ioapic_gsi_count(acpi_madt_ioapic_t* ioapic) {
return (__ioapic_read(ioapic, 1) & 0xff0000) >> 16;
}
acpi_madt_ioapic_t *ioapic_get_gsi(uint32_t gsi) {
for (uint32_t i = 0; i < madt_ioapic_len; i++) {
acpi_madt_ioapic_t* ioapic = madt_ioapic_list[i];
if (ioapic->gsi_base <= gsi && ioapic->gsi_base + ioapic_gsi_count(ioapic) > gsi)
return ioapic;
}
return NULL;
}
void ioapic_redirect_gsi(uint32_t lapic_id, uint8_t vec, uint32_t gsi, uint16_t flags, bool mask) {
acpi_madt_ioapic_t* ioapic = ioapic_get_gsi(gsi);
uint64_t redirect = vec;
if ((flags & (1 << 1)) != 0) {
redirect |= (1 << 13);
}
if ((flags & (1 << 3)) != 0) {
redirect |= (1 << 15);
}
if (mask) redirect |= (1 << 16);
else redirect &= ~(1 << 16);
redirect |= (uint64_t)lapic_id << 56;
uint32_t redtbl = (gsi - ioapic->gsi_base) * 2 + 16;
__ioapic_write(ioapic, redtbl, (uint32_t)redirect);
__ioapic_write(ioapic, redtbl + 1, (uint32_t)(redirect >> 32));
}
void ioapic_redir_irq(uint32_t lapic_id, uint8_t vec, uint8_t irq, bool mask) {
uint8_t idx = 0;
acpi_madt_iso_t* iso = NULL;
for (idx = 0; idx < madt_iso_len; ++idx) {
iso = madt_iso_list[idx];
if (iso->source == irq) { ioapic_redirect_gsi(lapic_id, vec, iso->gsi, iso->flags, mask); return; }
}
ioapic_redirect_gsi(lapic_id, vec, irq, 0, mask);
}

17
kernel/src/dev/ioapic.h Normal file
View file

@ -0,0 +1,17 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* ioapic.c - I/O APIC definitions.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define IOAPIC_VERSION 0x1
#define IOAPIC_REDTBL 0x10
void ioapic_init();
void ioapic_redir_irq(uint32_t lapic_id, uint8_t vec, uint8_t irq, bool mask);

33
kernel/src/dev/lapic.c Normal file
View file

@ -0,0 +1,33 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* lapic.c - Local APIC implementation.
*/
#include "dev/lapic.h"
#include "arch/x86_64/idt.h"
#include "lib/log.h"
#include "mm/pmm.h"
#include <stdint.h>
static uint64_t __lapic_pbase = 0xfee00000;
static uint64_t __lapic_vbase;
static void __lapic_write(uint32_t reg, uint32_t val) { *(volatile uint32_t *)(__lapic_vbase + reg) = val; }
static uint32_t __lapic_read(uint32_t reg) { return *(volatile uint32_t *)(__lapic_vbase + reg); }
static void __lapic_write_svr(uint32_t reg, lapic_svr_entry e) { *(volatile uint32_t *)(__lapic_vbase + reg) = *(uint32_t*)&e; }
void lapic_init() {
__lapic_vbase = higher_half(__lapic_pbase);
__lapic_write_svr(LAPIC_SVR, (lapic_svr_entry){ .swenabled = true, .vec = IDT_SPURIOUS_INT });
trace("lapic: Initialized\n");
}
void lapic_eoi() {
__lapic_write(LAPIC_EOI, 0x0);
}
uint32_t lapic_get_id() {
return __lapic_read(LAPIC_ID) >> LAPIC_ICDESTSHIFT;
}

26
kernel/src/dev/lapic.h Normal file
View file

@ -0,0 +1,26 @@
/*
* The Soaplin Kernel
* Copyright (C) 2025 The SILD Project
*
* lapic.h - Local APIC definitions.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define LAPIC_ID 0x20
#define LAPIC_SVR 0xF0
#define LAPIC_EOI 0xB0
#define LAPIC_ICDESTSHIFT 24
typedef struct {
uint8_t vec;
bool swenabled;
} lapic_svr_entry;
void lapic_init();
void lapic_eoi();
uint32_t lapic_get_id();

View file

@ -10,6 +10,7 @@
#include <stdbool.h>
#include <acpi/acpi.h>
#include <acpi/madt.h>
#include <arch/cpu.h>
#include <boot/limine.h>
#include <config.h>
@ -19,6 +20,8 @@
#include <lib/logoutputs_sk.h>
#include <mm/memop.h>
#include <mm/pmm.h>
#include "dev/ioapic.h"
#include "dev/lapic.h"
#include "mm/paging.h"
#include "mm/vma.h"
@ -37,7 +40,14 @@ void kmain(void) {
pg_init();
acpi_init();
madt_init();
lapic_init();
ioapic_init();
cpu_init_smp();
cpu_init_timer();
while (1)
;;
// We're done, just hang... for now.
hcf();
//hcf();
}

0
kernel/src/mm/pmm.md Normal file
View file

0
kernel/src/mm/vmm.c Normal file
View file