summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/libc/src/stdio/fopen.c2
-rw-r--r--sys/arch/amd64/amd64/hpet.c1
-rw-r--r--sys/arch/amd64/amd64/lapic.c1
-rw-r--r--sys/arch/amd64/amd64/machdep.c108
-rw-r--r--sys/arch/amd64/amd64/proc_machdep.c2
-rw-r--r--sys/arch/amd64/amd64/tsc.c109
-rw-r--r--sys/arch/amd64/conf/GENERIC2
-rw-r--r--sys/dev/acpi/uacpi.c13
-rw-r--r--sys/include/arch/amd64/asm.h10
-rw-r--r--sys/include/arch/amd64/cpu.h12
-rw-r--r--sys/include/arch/amd64/tsc.h55
-rw-r--r--sys/include/dev/timer.h5
-rw-r--r--sys/include/sys/sched.h2
-rw-r--r--sys/include/sys/sysctl.h5
-rw-r--r--sys/include/sys/vmstat.h48
-rw-r--r--sys/include/sys/workqueue.h101
-rw-r--r--sys/include/vm/physmem.h4
-rw-r--r--sys/include/vm/stat.h39
-rw-r--r--sys/kern/init_main.c4
-rw-r--r--sys/kern/kern_proc.c32
-rw-r--r--sys/kern/kern_spawn.c36
-rw-r--r--sys/kern/kern_sysctl.c6
-rw-r--r--sys/kern/kern_work.c274
-rw-r--r--sys/vm/vm_physmem.c39
-rw-r--r--sys/vm/vm_stat.c95
-rw-r--r--usr.bin/kstat/kstat.c52
-rw-r--r--usr.bin/sysctl/sysctl.c39
27 files changed, 1048 insertions, 48 deletions
diff --git a/lib/libc/src/stdio/fopen.c b/lib/libc/src/stdio/fopen.c
index c15dace..efeb577 100644
--- a/lib/libc/src/stdio/fopen.c
+++ b/lib/libc/src/stdio/fopen.c
@@ -51,6 +51,8 @@ fopen(const char *__restrict path, const char *__restrict mode)
seal |= (O_WRONLY | O_CREAT);
} else if (strcmp(mode, "r+") == 0) {
seal |= O_RDWR;
+ } else if (strcmp(mode, "rb") == 0) {
+ seal |= O_RDONLY;
} else {
return NULL;
}
diff --git a/sys/arch/amd64/amd64/hpet.c b/sys/arch/amd64/amd64/hpet.c
index 3b0ca46..9191bee 100644
--- a/sys/arch/amd64/amd64/hpet.c
+++ b/sys/arch/amd64/amd64/hpet.c
@@ -197,6 +197,7 @@ hpet_init(void)
timer.get_time_usec = hpet_time_usec;
timer.get_time_nsec = hpet_time_nsec;
timer.get_time_sec = hpet_time_sec;
+ timer.flags = TIMER_MONOTONIC;
register_timer(TIMER_GP, &timer);
return 0;
}
diff --git a/sys/arch/amd64/amd64/lapic.c b/sys/arch/amd64/amd64/lapic.c
index 022592c..ceb5428 100644
--- a/sys/arch/amd64/amd64/lapic.c
+++ b/sys/arch/amd64/amd64/lapic.c
@@ -364,5 +364,6 @@ lapic_init(void)
lapic_timer.name = "LAPIC_INTEGRATED_TIMER";
lapic_timer.stop = lapic_timer_stop;
lapic_timer.oneshot_us = lapic_timer_oneshot_us;
+ lapic_timer.flags = 0;
register_timer(TIMER_SCHED, &lapic_timer);
}
diff --git a/sys/arch/amd64/amd64/machdep.c b/sys/arch/amd64/amd64/machdep.c
index 3f8580a..3338caa 100644
--- a/sys/arch/amd64/amd64/machdep.c
+++ b/sys/arch/amd64/amd64/machdep.c
@@ -74,6 +74,12 @@
#define CPU_SMEP 0
#endif
+#if defined(__CPU_UMIP)
+#define CPU_UMIP __CPU_UMIP
+#else
+#define CPU_UMIP 0
+#endif
+
int ibrs_enable(void);
int simd_init(void);
void syscall_isr(void);
@@ -238,17 +244,90 @@ init_ipis(void)
}
static void
+cpu_get_vendor(struct cpu_info *ci)
+{
+ uint32_t unused, ebx, ecx, edx;
+ char vendor_str[13];
+
+ /*
+ * This CPUID returns a 12 byte CPU vendor string
+ * that we'll put together and use to detect the vendor.
+ */
+ CPUID(0, unused, ebx, ecx, edx);
+
+ /* Dword 0 */
+ vendor_str[0] = ebx & 0xFF;
+ vendor_str[1] = (ebx >> 8) & 0xFF;
+ vendor_str[2] = (ebx >> 16) & 0xFF;
+ vendor_str[3] = (ebx >> 24) & 0xFF;
+
+ /* Dword 1 */
+ vendor_str[4] = edx & 0xFF;
+ vendor_str[5] = (edx >> 8) & 0xFF;
+ vendor_str[6] = (edx >> 16) & 0xFF;
+ vendor_str[7] = (edx >> 24) & 0xFF;
+
+ /* Dword 2 */
+ vendor_str[8] = ecx & 0xFF;
+ vendor_str[9] = (ecx >> 8) & 0xFF;
+ vendor_str[10] = (ecx >> 16) & 0xFF;
+ vendor_str[11] = (ecx >> 24) & 0xFF;
+ vendor_str[12] = '\0';
+
+ /* Is this an AMD CPU? */
+ if (strcmp(vendor_str, "AuthenticAMD") == 0) {
+ ci->vendor = CPU_VENDOR_AMD;
+ return;
+ }
+
+ /* Is this an Intel CPU? */
+ if (strcmp(vendor_str, "GenuineIntel") == 0) {
+ ci->vendor = CPU_VENDOR_INTEL;
+ return;
+ }
+
+ /*
+ * Some buggy Intel CPUs report the string "GenuineIotel"
+ * instead of "GenuineIntel". This is rare but we should
+ * still handle it as it can happen. Probably a good idea
+ * to log it so the user can know about their rare CPU
+ * quirk and brag to their friends :~)
+ */
+ if (strcmp(vendor_str, "GenuineIotel") == 0) {
+ pr_trace_bsp("vendor_str=%s\n", vendor_str);
+ pr_trace_bsp("detected vendor string quirk\n");
+ ci->vendor = CPU_VENDOR_INTEL;
+ return;
+ }
+
+ ci->vendor = CPU_VENDOR_OTHER;
+}
+
+static void
cpu_get_info(struct cpu_info *ci)
{
- uint32_t eax, ebx, unused;
+ uint32_t unused, eax, ebx, ecx, edx;
uint8_t ext_model, ext_family;
+ /* Get the vendor information */
+ cpu_get_vendor(ci);
+
/* Extended features */
- CPUID(0x07, unused, ebx, unused, unused);
+ CPUID(0x07, unused, ebx, ecx, unused);
if (ISSET(ebx, BIT(7)))
ci->feat |= CPU_FEAT_SMEP;
if (ISSET(ebx, BIT(20)))
ci->feat |= CPU_FEAT_SMAP;
+ if (ISSET(ecx, BIT(2)))
+ ci->feat |= CPU_FEAT_UMIP;
+
+ /*
+ * Processor power management information bits as well
+ * as bits describing RAS capabilities
+ */
+ CPUID(0x80000007, unused, unused, unused, edx);
+ if (ISSET(edx, BIT(8)))
+ ci->feat |= CPU_FEAT_TSCINV;
/*
* Processor info and feature bits
@@ -278,6 +357,30 @@ cpu_get_info(struct cpu_info *ci)
}
}
+/*
+ * The CR4.UMIP bit prevents user programs from
+ * executing instructions related to accessing
+ * system memory structures. This should be enabled
+ * by default if supported.
+ */
+static void
+cpu_enable_umip(void)
+{
+ struct cpu_info *ci = this_cpu();
+ uint64_t cr4;
+
+ if (!CPU_UMIP) {
+ pr_trace_bsp("UMIP not configured\n");
+ return;
+ }
+
+ if (ISSET(ci->feat, CPU_FEAT_UMIP)) {
+ cr4 = amd64_read_cr4();
+ cr4 |= CR4_UMIP;
+ amd64_write_cr4(cr4);
+ }
+}
+
void
cpu_shootdown_tlb(vaddr_t va)
{
@@ -509,6 +612,7 @@ cpu_startup(struct cpu_info *ci)
cpu_get_info(ci);
cpu_enable_smep();
+ cpu_enable_umip();
enable_simd();
lapic_init();
diff --git a/sys/arch/amd64/amd64/proc_machdep.c b/sys/arch/amd64/amd64/proc_machdep.c
index ad807fe..a1d6563 100644
--- a/sys/arch/amd64/amd64/proc_machdep.c
+++ b/sys/arch/amd64/amd64/proc_machdep.c
@@ -256,7 +256,7 @@ sched_switch_to(struct trapframe *tf, struct proc *td)
/* Update stats */
cpustat = &ci->stat;
- cpustat->nswitch++;
+ atomic_inc_64(&cpustat->nswitch);
ci->curtd = td;
pcbp = &td->pcb;
diff --git a/sys/arch/amd64/amd64/tsc.c b/sys/arch/amd64/amd64/tsc.c
new file mode 100644
index 0000000..2111cd0
--- /dev/null
+++ b/sys/arch/amd64/amd64/tsc.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/cdefs.h>
+#include <sys/driver.h>
+#include <sys/syslog.h>
+#include <machine/tsc.h>
+#include <machine/asm.h>
+#include <machine/cpuid.h>
+
+/* See kconf(9) */
+#if defined(__USER_TSC)
+#define USER_TSC __USER_TSC
+#else
+#define USER_TSC 0
+#endif /* __USER_TSC */
+
+#define pr_trace(fmt, ...) kprintf("tsc: " fmt, ##__VA_ARGS__)
+#define pr_error(...) pr_trace(__VA_ARGS__)
+
+static uint64_t tsc_i = 0;
+
+uint64_t
+rdtsc_rel(void)
+{
+ return rdtsc() - tsc_i;
+}
+
+/*
+ * Check if the TSC and RDTSC instruction is
+ * supported on the current CPU.
+ *
+ * Returns zero if supported, otherwise a less
+ * than zero value is returned.
+ */
+static int
+tsc_check(void)
+{
+ uint32_t edx, unused;
+
+ CPUID(1, unused, unused, unused, edx);
+ if (ISSET(edx, BIT(4))) {
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+static int
+tsc_init(void)
+{
+ uint64_t cr4;
+ int error;
+
+ /* Is the TSC even supported? */
+ if ((error = tsc_check()) != 0) {
+ pr_error("TSC not supported by machine\n");
+ return error;
+ }
+
+ cr4 = amd64_read_cr4();
+ tsc_i = rdtsc();
+ pr_trace("initial count @ %d\n", rdtsc_rel());
+
+ /*
+ * If we USER_TSC is configured to "yes" then
+ * we'll need to enable the 'rdtsc' instruction
+ * in user mode.
+ */
+ if (!USER_TSC) {
+ cr4 &= ~CR4_TSD;
+ } else {
+ cr4 |= CR4_TSD;
+ }
+
+ amd64_write_cr4(cr4);
+ return 0;
+}
+
+DRIVER_EXPORT(tsc_init, "x86-tsc");
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 6f573f3..6bf3af5 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -7,6 +7,8 @@
//
option SPECTRE_IBRS no // Enable the IBRS CPU feature
option SERIAL_DEBUG yes // Enable kmsg serial logging
+option CPU_UMIP yes // Enable User-mode Instruction Prevention
option USER_KMSG no // Show kmsg in user consoles
+option USER_TSC no // Enable 'rdtsc' in user mode
option CPU_SMEP yes // Supervisor Memory Exec Protection
option I8042_POLL yes // Use polling for the i8042
diff --git a/sys/dev/acpi/uacpi.c b/sys/dev/acpi/uacpi.c
index ffec436..6c2bf50 100644
--- a/sys/dev/acpi/uacpi.c
+++ b/sys/dev/acpi/uacpi.c
@@ -55,6 +55,9 @@
#include <vm/vm.h>
#include <string.h>
+#define pr_trace(fmt, ...) kprintf("acpi: " fmt, ##__VA_ARGS__)
+#define pr_error(...) pr_trace(__VA_ARGS__)
+
typedef struct {
uacpi_io_addr base;
uacpi_size length;
@@ -633,25 +636,25 @@ uacpi_init(void)
ret = uacpi_initialize(0);
if (uacpi_unlikely_error(ret)) {
- kprintf("uacpi init error: %s\n", uacpi_status_to_string(ret));
+ pr_error("uacpi init error: %s\n", uacpi_status_to_string(ret));
return -1;
}
ret = uacpi_namespace_load();
if (uacpi_unlikely_error(ret)) {
- kprintf("uacpi namespace load error: %s\n", uacpi_status_to_string(ret));
+ pr_error("uacpi namespace load error: %s\n", uacpi_status_to_string(ret));
return -1;
}
ret = uacpi_namespace_initialize();
if (uacpi_unlikely_error(ret)) {
- kprintf("uacpi namespace init error: %s\n", uacpi_status_to_string(ret));
+ pr_error("uacpi namespace init error: %s\n", uacpi_status_to_string(ret));
return -1;
}
ret = uacpi_finalize_gpe_initialization();
if (uacpi_unlikely_error(ret)) {
- kprintf("uacpi GPE init error: %s\n", uacpi_status_to_string(ret));
+ pr_error("uacpi GPE init error: %s\n", uacpi_status_to_string(ret));
return -1;
}
@@ -661,7 +664,7 @@ uacpi_init(void)
);
if (uacpi_unlikely_error(ret)) {
- kprintf("failed to install power button event: %s\n",
+ pr_error("failed to install power button event: %s\n",
uacpi_status_to_string(ret)
);
return -1;
diff --git a/sys/include/arch/amd64/asm.h b/sys/include/arch/amd64/asm.h
index 8d2c812..aca49d2 100644
--- a/sys/include/arch/amd64/asm.h
+++ b/sys/include/arch/amd64/asm.h
@@ -34,6 +34,16 @@
#include <sys/param.h>
#include <machine/msr.h>
+/* CR4 bits */
+#define CR4_TSD BIT(2) /* Timestamp disable */
+#define CR4_DE BIT(3) /* Debugging extensions */
+#define CR4_PSE BIT(4) /* Page size extensions */
+#define CR4_PCE BIT(8) /* Performance monitoring counter enable */
+#define CR4_UMIP BIT(11) /* User mode instruction prevention */
+#define CR4_LA57 BIT(12) /* Level 5 paging enable */
+#define CR4_VMXE BIT(13) /* Virtual machine extensions enable */
+#define CR4_SMXE BIT(14) /* Safer mode extensions enable */
+
/*
* Contains information for the current
* core. Stored in %GS.
diff --git a/sys/include/arch/amd64/cpu.h b/sys/include/arch/amd64/cpu.h
index 4586163..6ed675e 100644
--- a/sys/include/arch/amd64/cpu.h
+++ b/sys/include/arch/amd64/cpu.h
@@ -42,14 +42,22 @@
#define CPU_IRQ(IRQ_N) (BIT((IRQ_N)) & 0xFF)
/* Feature bits */
-#define CPU_FEAT_SMAP BIT(0)
-#define CPU_FEAT_SMEP BIT(1)
+#define CPU_FEAT_SMAP BIT(0)
+#define CPU_FEAT_SMEP BIT(1)
+#define CPU_FEAT_UMIP BIT(2)
+#define CPU_FEAT_TSCINV BIT(3) /* TSC invariant */
+
+/* CPU vendors */
+#define CPU_VENDOR_OTHER 0x00000000
+#define CPU_VENDOR_INTEL 0x00000001
+#define CPU_VENDOR_AMD 0x00000002
typedef uint16_t ipi_pend_t;
struct cpu_info {
uint32_t apicid;
uint32_t feat;
+ uint32_t vendor; /* Vendor (see CPU_VENDOR_*) */
uint8_t ipi_dispatch : 1; /* 1: IPIs being dispatched */
uint8_t ipi_id;
ipi_pend_t ipi_pending[N_IPIVEC];
diff --git a/sys/include/arch/amd64/tsc.h b/sys/include/arch/amd64/tsc.h
new file mode 100644
index 0000000..d9eed4f
--- /dev/null
+++ b/sys/include/arch/amd64/tsc.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MACHINE_TSC_H_
+#define _MACHINE_TSC_H_
+
+#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <sys/param.h>
+
+uint64_t rdtsc_rel(void);
+
+__always_inline static inline uint64_t
+rdtsc(void)
+{
+ uint32_t lo, hi;
+
+ __ASMV(
+ "rdtsc"
+ : "=d" (hi),
+ "=a" (lo)
+ :
+ : "memory"
+ );
+
+ return COMBINE32(hi, lo);
+}
+
+#endif /* !_MACHINE_TSC_H_ */
diff --git a/sys/include/dev/timer.h b/sys/include/dev/timer.h
index fe91323..2ca6d62 100644
--- a/sys/include/dev/timer.h
+++ b/sys/include/dev/timer.h
@@ -31,11 +31,15 @@
#define _DEV_TIMER_H_
#include <sys/types.h>
+#include <sys/param.h>
/* Timer IDs */
#define TIMER_SCHED 0x00000000U /* Scheduler reserved timer */
#define TIMER_GP 0x00000001U /* General purpose timer */
+/* Timer flags */
+#define TIMER_MONOTONIC BIT(0)
+
/* Number of timer IDs, adjust when adding timer IDs */
#define TIMER_ID_COUNT 2
@@ -79,6 +83,7 @@ struct timer {
void(*oneshot_ms)(size_t ms);
void(*oneshot_us)(size_t ms);
void(*stop)(void);
+ uint8_t flags;
};
tmrr_status_t register_timer(timer_id_t id, const struct timer *tmr);
diff --git a/sys/include/sys/sched.h b/sys/include/sys/sched.h
index 19ceb7e..8b0ba02 100644
--- a/sys/include/sys/sched.h
+++ b/sys/include/sys/sched.h
@@ -42,7 +42,7 @@
* @idle: Number of milliseconds idle
*/
struct sched_cpu {
- uint32_t nswitch;
+ uint64_t nswitch;
};
/*
diff --git a/sys/include/sys/sysctl.h b/sys/include/sys/sysctl.h
index 3b8d3c7..ce7510d 100644
--- a/sys/include/sys/sysctl.h
+++ b/sys/include/sys/sysctl.h
@@ -56,6 +56,11 @@
#define HW_MACHINE 7
/*
+ * List of 'proc.*' identifiers
+ */
+#define PROC_COUNT 8
+
+/*
* Option types (i.e., int, string, etc) for
* sysctl entries.
*
diff --git a/sys/include/sys/vmstat.h b/sys/include/sys/vmstat.h
new file mode 100644
index 0000000..b7faeb2
--- /dev/null
+++ b/sys/include/sys/vmstat.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_VMSTAT_H_
+#define _SYS_VMSTAT_H_
+
+#include <sys/types.h>
+
+/*
+ * Virtual memory statistics
+ *
+ * @mem_avail: Available memory in MiB
+ * @mem_used: Allocated memory in MiB
+ * @mem_total: Total system memory in MiB
+ */
+struct vm_stat {
+ uint32_t mem_avail;
+ uint32_t mem_used;
+ size_t mem_total;
+};
+
+#endif /* !_VM_STAT_H_ */
diff --git a/sys/include/sys/workqueue.h b/sys/include/sys/workqueue.h
new file mode 100644
index 0000000..9925f79
--- /dev/null
+++ b/sys/include/sys/workqueue.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_WORKQUEUE_H_
+#define _SYS_WORKQUEUE_H_
+
+#if defined(_KERNEL)
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+
+struct workqueue;
+struct work;
+
+/*
+ * A work function can either refer to a work thread
+ * entry (or actual work to be done
+ */
+typedef void(*workfunc_t)(struct workqueue *wqp, struct work *wp);
+
+/*
+ * Represents work that may be added to a
+ * workqueue.
+ *
+ * @name: Name of this work/task [i]
+ * @data: Optional data to be passed with work [p]
+ * @func: Function with work to be done [p]
+ * @cookie: Used for validating the work structure [i]
+ *
+ * Field attributes:
+ * - [i]: Used internally
+ * - [p]: Used as parameter
+ */
+struct work {
+ char *name;
+ void *data;
+ workfunc_t func;
+ TAILQ_ENTRY(work) link;
+};
+
+/*
+ * A workqueue contains tasks that are
+ * queued up to be completed in their own
+ * thread context.
+ *
+ * @name: Name of workqueue.
+ * @work: Start of the workqueue
+ * @ipl: IPL that work here must run with
+ * @max_work: Max number of jobs that can be queued
+ * @nwork: Number of tasks to be done
+ * @cookie: For validating workqueues
+ * @worktd: Thread associated with the workqueue
+ * @lock: Protects the workqueue
+ */
+struct workqueue {
+ char *name;
+ TAILQ_HEAD(, work) work;
+ uint8_t ipl;
+ size_t max_work;
+ ssize_t nwork;
+ uint16_t cookie;
+ struct proc *worktd;
+ struct mutex *lock;
+};
+
+struct workqueue *workqueue_new(const char *name, size_t max_work, int ipl);
+
+int workqueue_enq(struct workqueue *wqp, const char *name, struct work *wp);
+int workqueue_destroy(struct workqueue *wqp);
+int work_destroy(struct work *wp);
+
+#endif /* !_KERNEL */
+#endif /* !_SYS_WORKQUEUE_H_ */
diff --git a/sys/include/vm/physmem.h b/sys/include/vm/physmem.h
index ae11530..3f1da61 100644
--- a/sys/include/vm/physmem.h
+++ b/sys/include/vm/physmem.h
@@ -32,6 +32,10 @@
#include <sys/types.h>
+uint32_t vm_mem_used(void);
+uint32_t vm_mem_free(void);
+size_t vm_mem_total(void);
+
void vm_physmem_init(void);
uintptr_t vm_alloc_frame(size_t count);
void vm_free_frame(uintptr_t base, size_t count);
diff --git a/sys/include/vm/stat.h b/sys/include/vm/stat.h
new file mode 100644
index 0000000..7e9a4a9
--- /dev/null
+++ b/sys/include/vm/stat.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _VM_STAT_H_
+#define _VM_STAT_H_
+
+#include <sys/types.h>
+#include <sys/vmstat.h>
+
+int vm_stat_get(struct vm_stat *vmstat);
+void vm_stat_init(void);
+
+#endif /* !_VM_STAT_H_ */
diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c
index 541355a..577b7ec 100644
--- a/sys/kern/init_main.c
+++ b/sys/kern/init_main.c
@@ -43,6 +43,7 @@
#include <machine/cpu.h>
#include <machine/cdefs.h>
#include <vm/vm.h>
+#include <vm/stat.h>
#include <string.h>
#define _START_PATH "/usr/sbin/init"
@@ -104,6 +105,9 @@ main(void)
/* Init the virtual file system */
vfs_init();
+ /* Init vmstats */
+ vm_stat_init();
+
/* Expose the console to devfs */
cons_expose();
diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c
index 87dcc74..8bc5680 100644
--- a/sys/kern/kern_proc.c
+++ b/sys/kern/kern_proc.c
@@ -29,14 +29,18 @@
#include <sys/types.h>
#include <sys/proc.h>
+#include <sys/errno.h>
#include <sys/cdefs.h>
#include <sys/vnode.h>
+#include <sys/tree.h>
#include <sys/syscall.h>
#include <sys/filedesc.h>
#include <sys/fcntl.h>
#include <string.h>
#include <crc32.h>
+extern volatile size_t g_nthreads;
+
pid_t
getpid(void)
{
@@ -50,7 +54,6 @@ getpid(void)
return td->pid;
}
-
pid_t
getppid(void)
{
@@ -100,6 +103,33 @@ proc_coredump(struct proc *td, uintptr_t fault_addr)
fd_close(fd);
}
+int
+proc_init(struct proc *td, struct proc *parent)
+{
+ struct mmap_lgdr *mlgdr;
+
+ mlgdr = dynalloc(sizeof(*mlgdr));
+ if (mlgdr == NULL) {
+ return -ENOMEM;
+ }
+
+ /* Add to parent leafq */
+ TAILQ_INSERT_TAIL(&parent->leafq, td, leaf_link);
+ atomic_inc_int(&parent->nleaves);
+ atomic_inc_64(&g_nthreads);
+ td->parent = parent;
+ td->exit_status = -1;
+ td->cred = parent->cred;
+
+ /* Initialize the mmap ledger */
+ mlgdr->nbytes = 0;
+ RBT_INIT(lgdr_entries, &mlgdr->hd);
+ td->mlgdr = mlgdr;
+ td->flags |= PROC_WAITED;
+ signals_init(td);
+ return 0;
+}
+
scret_t
sys_getpid(struct syscall_args *scargs)
{
diff --git a/sys/kern/kern_spawn.c b/sys/kern/kern_spawn.c
index b9551f3..7962ced 100644
--- a/sys/kern/kern_spawn.c
+++ b/sys/kern/kern_spawn.c
@@ -34,10 +34,8 @@
#include <sys/mman.h>
#include <sys/systm.h>
#include <sys/errno.h>
-#include <sys/atomic.h>
#include <sys/syslog.h>
#include <sys/syscall.h>
-#include <sys/atomic.h>
#include <sys/signal.h>
#include <sys/limits.h>
#include <sys/sched.h>
@@ -50,7 +48,6 @@
#define ARGVP_MAX (ARG_MAX / sizeof(void *))
static size_t next_pid = 1;
-extern volatile size_t g_nthreads;
/*
* TODO: envp
@@ -145,7 +142,6 @@ pid_t
spawn(struct proc *cur, void(*func)(void), void *p, int flags, struct proc **newprocp)
{
struct proc *newproc;
- struct mmap_lgdr *mlgdr;
int error;
pid_t pid;
@@ -156,19 +152,10 @@ spawn(struct proc *cur, void(*func)(void), void *p, int flags, struct proc **new
return -ENOMEM;
}
- mlgdr = dynalloc(sizeof(*mlgdr));
- if (mlgdr == NULL) {
- dynfree(newproc);
- try_free_data(p);
- pr_error("could not alloc proc mlgdr (-ENOMEM)\n");
- return -ENOMEM;
- }
-
memset(newproc, 0, sizeof(*newproc));
error = md_spawn(newproc, cur, (uintptr_t)func);
if (error < 0) {
dynfree(newproc);
- dynfree(mlgdr);
try_free_data(p);
pr_error("error initializing proc\n");
return error;
@@ -184,23 +171,16 @@ spawn(struct proc *cur, void(*func)(void), void *p, int flags, struct proc **new
cur->flags |= PROC_LEAFQ;
}
- /* Add to parent leafq */
- TAILQ_INSERT_TAIL(&cur->leafq, newproc, leaf_link);
- atomic_inc_int(&cur->nleaves);
- newproc->parent = cur;
- newproc->data = p;
- newproc->exit_status = -1;
- newproc->cred = cur->cred;
-
- /* Initialize the mmap ledger */
- mlgdr->nbytes = 0;
- RBT_INIT(lgdr_entries, &mlgdr->hd);
- newproc->mlgdr = mlgdr;
- newproc->flags |= PROC_WAITED;
+ error = proc_init(newproc, cur);
+ if (error < 0) {
+ dynfree(newproc);
+ try_free_data(p);
+ pr_error("error initializing proc\n");
+ return error;
+ }
- atomic_inc_64(&g_nthreads);
+ newproc->data = p;
newproc->pid = next_pid++;
- signals_init(newproc);
sched_enqueue_td(newproc);
pid = newproc->pid;
return pid;
diff --git a/sys/kern/kern_sysctl.c b/sys/kern/kern_sysctl.c
index a4c16bb..1f5e578 100644
--- a/sys/kern/kern_sysctl.c
+++ b/sys/kern/kern_sysctl.c
@@ -40,6 +40,7 @@
HYRA_VERSION " " \
HYRA_BUILDDATE
+extern size_t g_nthreads;
static uint32_t pagesize = DEFAULT_PAGESIZE;
static char machine[] = HYRA_ARCH;
static char hyra[] = "Hyra";
@@ -62,7 +63,10 @@ static struct sysctl_entry common_optab[] = {
/* 'hw.*' */
[HW_PAGESIZE] = { HW_PAGESIZE, SYSCTL_OPTYPE_INT_RO, &pagesize },
[HW_NCPU] = { HW_NCPU, SYSCTL_OPTYPE_INT, NULL },
- [HW_MACHINE] = {HW_MACHINE, SYSCTL_OPTYPE_STR_RO, &machine }
+ [HW_MACHINE] = {HW_MACHINE, SYSCTL_OPTYPE_STR_RO, &machine },
+
+ /* 'proc.*' */
+ [PROC_COUNT] = { PROC_COUNT, SYSCTL_OPTYPE_INT_RO, &g_nthreads }
};
static int
diff --git a/sys/kern/kern_work.c b/sys/kern/kern_work.c
new file mode 100644
index 0000000..918af89
--- /dev/null
+++ b/sys/kern/kern_work.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/panic.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+#include <sys/syslog.h>
+#include <sys/workqueue.h>
+#include <vm/dynalloc.h>
+#include <string.h>
+
+#define pr_trace(fmt, ...) kprintf("workq: " fmt, ##__VA_ARGS__)
+#define pr_error(...) pr_trace(__VA_ARGS__)
+
+extern struct proc g_proc0;
+
+/*
+ * The workqueue cookie value that is used for
+ * verifying if a workqueue object is properly
+ * set up or not.
+ */
+#define WQ_COOKIE 0xFC0B
+
+/*
+ * A worker services work in the queue
+ * and there is one per workqueue.
+ */
+static void
+workqueue_worker(void)
+{
+ struct proc *td;
+ struct workqueue *wqp;
+ struct work *wp;
+
+ td = this_td();
+ if ((wqp = td->data) == NULL) {
+ panic("no workqueue in thread\n");
+ }
+
+ /*
+ * Weird things can happen, just be careful
+ * here...
+ */
+ if (wqp->cookie != WQ_COOKIE) {
+ panic("bad WQ_COOKIE in worker\n");
+ }
+
+ for (;;) {
+ mutex_acquire(wqp->lock, 0);
+ wp = TAILQ_FIRST(&wqp->work);
+
+ /* Try again later if empty */
+ if (wp == NULL) {
+ mutex_release(wqp->lock);
+ sched_yield();
+ continue;
+ }
+
+ wp->func(wqp, wp);
+ TAILQ_REMOVE(&wqp->work, wp, link);
+
+ /*
+ * Decrement the amount of work that is
+ * left to get done. Check for underflows
+ * which should not happen unless something
+ * clobbers the fields.
+ */
+ if ((--wqp->nwork) < 0) {
+ panic("wqp nwork underflow\n");
+ }
+
+ mutex_release(wqp->lock);
+ sched_yield();
+ }
+}
+
+/*
+ * Allocates a new work queue that may be used
+ * to hold queued up tasks.
+ *
+ * @name: Name to give the workqueue
+ * @max_work: Maximum number of jobs to be added
+ * @ipl: IPL that the work must operate in
+ *
+ * Returns a pointer to the new workqueue on success,
+ * otherwise a value of NULL is returned.
+ */
+struct workqueue *
+workqueue_new(const char *name, size_t max_work, int ipl)
+{
+ struct workqueue *wqp;
+ struct proc *td;
+
+ td = this_td();
+ if (__unlikely(td == NULL)) {
+ pr_error("no thread in workqueue_new()\n");
+ return NULL;
+ }
+
+ wqp = dynalloc(sizeof(*wqp));
+ if (wqp == NULL) {
+ return NULL;
+ }
+
+ wqp->name = strdup(name);
+ TAILQ_INIT(&wqp->work);
+ wqp->ipl = ipl;
+ wqp->max_work = max_work;
+ wqp->nwork = 0;
+ wqp->cookie = WQ_COOKIE;
+ wqp->lock = mutex_new(wqp->name);
+
+ /*
+ * We need to spawn the work thread which
+ * is behind the management of this specific
+ * workqueue. It typically does something like
+ * dequeuing at the head of the workqueue, performing
+ * the work, cleaning up as needed and dequeuing the
+ * next and waiting if there are none yet.
+ */
+ spawn(
+ &g_proc0, workqueue_worker,
+ wqp, 0,
+ &wqp->worktd
+ );
+
+ return wqp;
+}
+
+/*
+ * Enqueue a work item onto a specific
+ * workqueue.
+ *
+ * @wqp: Pointer to specific workqueue
+ * @name: Name to set for work unit
+ * @wp: Pointer to work that should be enqueued
+ *
+ * Returns zero on success, otherwise a less than
+ * zero value is returned.
+ */
+int
+workqueue_enq(struct workqueue *wqp, const char *name, struct work *wp)
+{
+ if (wqp == NULL || wp == NULL) {
+ return -EINVAL;
+ }
+
+ if (name == NULL) {
+ return -EINVAL;
+ }
+
+ /* Verify that we have a valid workqueue */
+ if (__unlikely(wqp->cookie != WQ_COOKIE)) {
+ panic("workq: bad cookie on work enqueue\n");
+ }
+
+ wp->name = strdup(name);
+ mutex_acquire(wqp->lock, 0);
+
+ /*
+ * If we have reached the max amount of jobs
+ * that we can enqueue here, just log it and
+ * bail.
+ */
+ if (wqp->nwork >= wqp->max_work) {
+ pr_error("max jobs reached for '%s'\n", wqp->name);
+ mutex_release(wqp->lock);
+ return -EAGAIN;
+ }
+
+ TAILQ_INSERT_TAIL(&wqp->work, wp, link);
+ ++wqp->nwork;
+ mutex_release(wqp->lock);
+ return 0;
+}
+
+/*
+ * Destroy a workqueue and free resources
+ * associated with it.
+ *
+ * @wqp: Pointer to workqueue to destroy
+ *
+ * Returns zero on success, otherwise a less
+ * than zero value is returned.
+ */
+int
+workqueue_destroy(struct workqueue *wqp)
+{
+ if (wqp == NULL) {
+ return -EINVAL;
+ }
+
+ /* Should not happen but just make sure */
+ if (__unlikely(wqp->cookie != WQ_COOKIE)) {
+ panic("workq: bad cookie on destroy\n");
+ }
+
+ /* Free the name if we have it */
+ if (wqp->name != NULL) {
+ dynfree(wqp->name);
+ }
+
+ if (wqp->lock != NULL) {
+ mutex_free(wqp->lock);
+ }
+
+ /* Brutally murder any workthreads */
+ if (wqp->worktd != NULL) {
+ exit1(wqp->worktd, 0);
+ wqp->worktd = NULL;
+ }
+
+ /*
+ * Zero before we free for security reasons, we
+ * don't really know what will be queued up but
+ * for certain things, it is best if we make it
+ * as if it never existed in the first place.
+ *
+ * XXX: There is no need to free the workqueue here as
+ * we had to pass it to spawn() to run the worker.
+ *
+ * During an exit, spawn() will free the thread data
+ * meaning this is already cleaned up.
+ */
+ memset(wqp, 0, sizeof(*wqp));
+ return 0;
+}
+
+/*
+ * Cleanup after work
+ *
+ * @wp: Work to clean up
+ */
+int
+work_destroy(struct work *wp)
+{
+ if (wp == NULL) {
+ return -EINVAL;
+ }
+
+ if (wp->name != NULL) {
+ dynfree(wp->name);
+ }
+
+ return 0;
+}
diff --git a/sys/vm/vm_physmem.c b/sys/vm/vm_physmem.c
index 89f9ee6..b6e7347 100644
--- a/sys/vm/vm_physmem.c
+++ b/sys/vm/vm_physmem.c
@@ -36,6 +36,11 @@
#include <vm/vm.h>
#include <string.h>
+#define BYTES_PER_MIB 8388608
+
+static size_t pages_free = 0;
+static size_t pages_used = 0;
+static size_t pages_total = 0;
static size_t highest_frame_idx = 0;
static size_t bitmap_size = 0;
static size_t bitmap_free_start = 0;
@@ -60,9 +65,11 @@ physmem_populate_bitmap(void)
for (size_t i = 0; i < resp->entry_count; ++i) {
ent = resp->entries[i];
+ pages_total += ent->length / DEFAULT_PAGESIZE;
if (ent->type != LIMINE_MEMMAP_USABLE) {
/* This memory is not usable */
+ pages_used += ent->length / DEFAULT_PAGESIZE;
continue;
}
@@ -73,6 +80,8 @@ physmem_populate_bitmap(void)
for (size_t j = 0; j < ent->length; j += DEFAULT_PAGESIZE) {
clrbit(bitmap, (ent->base + j) / DEFAULT_PAGESIZE);
}
+
+ pages_free += ent->length / DEFAULT_PAGESIZE;
}
}
@@ -203,6 +212,36 @@ vm_free_frame(uintptr_t base, size_t count)
spinlock_release(&lock);
}
+/*
+ * Return the amount of memory in MiB that is
+ * currently allocated.
+ */
+uint32_t
+vm_mem_used(void)
+{
+ return (pages_used * DEFAULT_PAGESIZE) / BYTES_PER_MIB;
+}
+
+/*
+ * Return the amount of memory in MiB that is
+ * currently free.
+ */
+uint32_t
+vm_mem_free(void)
+{
+ return (pages_free * DEFAULT_PAGESIZE) / BYTES_PER_MIB;
+}
+
+/*
+ * Return the total amount of memory supported
+ * by the machine.
+ */
+size_t
+vm_mem_total(void)
+{
+ return (pages_total * DEFAULT_PAGESIZE) / BYTES_PER_MIB;
+}
+
void
vm_physmem_init(void)
{
diff --git a/sys/vm/vm_stat.c b/sys/vm/vm_stat.c
new file mode 100644
index 0000000..3e39047
--- /dev/null
+++ b/sys/vm/vm_stat.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <fs/ctlfs.h>
+#include <vm/physmem.h>
+#include <vm/vm.h>
+#include <vm/stat.h>
+#include <string.h>
+
+#include <sys/syslog.h>
+
+static struct ctlops vm_stat_ctl;
+
+/*
+ * ctlfs hook to read the virtual memory
+ * statistics.
+ */
+static int
+vm_stat_read(struct ctlfs_dev *cdp, struct sio_txn *sio)
+{
+ struct vm_stat stat;
+ int error;
+
+ if (sio->len > sizeof(stat)) {
+ sio->len = sizeof(stat);
+ }
+
+ error = vm_stat_get(&stat);
+ if (error < 0) {
+ return error;
+ }
+
+ memcpy(sio->buf, &stat, sio->len);
+ return sio->len;
+}
+
+int
+vm_stat_get(struct vm_stat *vmstat)
+{
+ if (vmstat == NULL) {
+ return -EINVAL;
+ }
+
+ vmstat->mem_avail = vm_mem_free();
+ vmstat->mem_used = vm_mem_used();
+ vmstat->mem_total = vm_mem_total();
+ return 0;
+}
+
+void
+vm_stat_init(void)
+{
+ char devname[] = "vm";
+ struct ctlfs_dev ctl;
+
+ /* Register a stat control file */
+ ctl.mode = 0444;
+ ctlfs_create_node(devname, &ctl);
+ ctl.devname = devname;
+ ctl.ops = &vm_stat_ctl;
+ ctlfs_create_entry("stat", &ctl);
+}
+
+static struct ctlops vm_stat_ctl = {
+ .read = vm_stat_read,
+ .write = NULL
+};
diff --git a/usr.bin/kstat/kstat.c b/usr.bin/kstat/kstat.c
index e3b7e6a..cbbe602 100644
--- a/usr.bin/kstat/kstat.c
+++ b/usr.bin/kstat/kstat.c
@@ -28,10 +28,51 @@
*/
#include <sys/sched.h>
+#include <sys/vmstat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
+#define MIB_PER_GIB 1024
+
+static void
+print_size_mib(const char *name, size_t mib)
+{
+ if (name == NULL) {
+ return;
+ }
+
+ if (mib >= MIB_PER_GIB) {
+ printf("%s: %d GiB\n", name, mib / MIB_PER_GIB);
+ } else {
+ printf("%s: %d MiB\n", name, mib);
+ }
+}
+
+static void
+get_vm_stat(void)
+{
+ struct vm_stat vmstat;
+ int retval, fd;
+
+ fd = open("/ctl/vm/stat", O_RDONLY);
+ if (fd < 0) {
+ printf("failed to open '/ctl/vm/stat'\n");
+ return;
+ }
+
+ retval = read(fd, &vmstat, sizeof(vmstat));
+ if (retval <= 0) {
+ printf("failed to read vmstat\n");
+ return;
+ }
+
+ close(fd);
+ print_size_mib("memory available", vmstat.mem_avail);
+ print_size_mib("memory used", vmstat.mem_used);
+ print_size_mib("memory total", vmstat.mem_total);
+}
+
static void
get_sched_stat(void)
{
@@ -56,12 +97,10 @@ get_sched_stat(void)
nonline = (stat.ncpu - noffline);
online_percent = (uint16_t)(((double)nonline / (nonline + noffline)) * 100);
- printf("-------------------------------\n");
- printf("Number of tasks: %d\n", stat.nproc);
- printf("Number of cores online: %d\n", stat.ncpu);
- printf("Scheduler quantum: %d usec\n", stat.quantum_usec);
+ printf("number of tasks: %d\n", stat.nproc);
+ printf("number of cores online: %d\n", stat.ncpu);
+ printf("scheduler quantum: %d usec\n", stat.quantum_usec);
printf("CPU is %d%% online\n", online_percent);
- printf("-------------------------------\n");
/*
* Log out some per-cpu information
@@ -75,6 +114,9 @@ get_sched_stat(void)
int
main(void)
{
+ printf("-- scheduler statistics --\n");
get_sched_stat();
+ printf("-- memory statistics --\n");
+ get_vm_stat();
return 0;
}
diff --git a/usr.bin/sysctl/sysctl.c b/usr.bin/sysctl/sysctl.c
index d4275a7..4a84484 100644
--- a/usr.bin/sysctl/sysctl.c
+++ b/usr.bin/sysctl/sysctl.c
@@ -46,13 +46,18 @@
#define NAME_NCPU "ncpu"
#define NAME_MACHINE "machine"
+/* Proc var string constants */
+#define NAME_COUNT "count"
+
/* Name start string constants */
#define NAME_KERN "kern"
#define NAME_HW "hw"
+#define NAME_PROC "proc"
/* Name start int constants */
#define NAME_DEF_KERN 0
#define NAME_DEF_HW 1
+#define NAME_DEF_PROC 2
/*
* Print the contents read from a sysctl
@@ -105,6 +110,12 @@ name_to_def(const char *name)
}
return -1;
+ case 'p':
+ if (strcmp(name, NAME_PROC) == 0) {
+ return NAME_DEF_PROC;
+ }
+
+ return -1;
}
return -1;
@@ -178,6 +189,28 @@ hw_node(const char *node, bool *is_str)
}
/*
+ * Handle parsing of 'proc.*' node names
+ *
+ * @node: Node name to parse
+ * @is_str: Set to true if string
+ */
+static int
+proc_node(const char *node, bool *is_str)
+{
+ switch (*node) {
+ case 'c':
+ if (strcmp(node, NAME_COUNT) == 0) {
+ *is_str = false;
+ return PROC_COUNT;
+ }
+
+ return -1;
+ }
+
+ return -1;
+}
+
+/*
* Convert string node to a sysctl name
* definition.
*
@@ -215,6 +248,8 @@ node_to_def(int name, const char *node, bool *is_str)
return kern_node(node, is_str);
case NAME_DEF_HW:
return hw_node(node, is_str);
+ case NAME_DEF_PROC:
+ return proc_node(node, is_str);
}
return -1;
@@ -240,12 +275,12 @@ main(int argc, char **argv)
p = strtok(var, ".");
if (p == NULL) {
- printf("sysctl: bad var\n");
+ printf("sysctl: bad var \"%s\"\n", p);
return -1;
}
if ((root = name_to_def(p)) < 0) {
- printf("sysctl: bad var \"%s\"", p);
+ printf("sysctl: bad var \"%s\"\n", p);
return root;
}