Compare commits

..

26 commits

Author SHA1 Message Date
Myles "Mellurboo" Wilson
85c93cbcfa multithreaded makefile
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
2026-05-14 17:00:22 +01:00
boreddevnl
deea3eaabc brand: change default wallpaper to drift.png 2026-05-14 16:21:28 +02:00
Lluciocc
7ffa0c170f
PR #49: Window Manager Graphics Optimisations 2026-05-14 15:39:56 +02:00
mellurboo
fd20a5df41 Window Manager Graphics Optimisations 2026-05-14 14:28:27 +01:00
Myles "Mellurboo" Wilson
637670f8b0
ACPI: I2C Device Enumeration & Logging Function Accessability (#46)
* ACPI I2C Device Enumeration and Updated docs with better access to logging techniques

* updated copyright notices

bit of a hastle on my end, still getting used to being this cool
2026-05-14 15:22:32 +02:00
Lluciocc
86d05c6040
Merge pull request #48 from janevers-sys/main
Add GPL license header to find.c
2026-05-14 13:07:51 +02:00
Myles "Mellurboo" Wilson
a78bd424fc
Merge pull request #32 from Lluciocc/cmds
Adding time
2026-05-14 12:07:38 +01:00
Jan
faf56e56d9
Update copyright notice in find.c 2026-05-14 13:06:02 +02:00
Jan
478af73f63
Add GPL license header to find.c
Add copyright header and license information
2026-05-14 12:57:16 +02:00
Lluciocc
46566c766c
Update bsh.c 2026-05-14 10:20:56 +02:00
Lluciocc
7339183bf1
Add 'time' command to terminal usage documentation 2026-05-14 01:00:15 +02:00
Lluciocc
7d1426de46
Merge branch 'main' into cmds 2026-05-14 00:53:06 +02:00
Lluciocc
fdd25b31cd Feat: Implementing time command inside bsh.c 2026-05-14 00:47:47 +02:00
Lluciocc
29e1b362ff replacing with strcmp && Adding comments && adding helper when the command don't exist 2026-05-13 09:17:08 +02:00
Lluciocc
69d5f8feff Update header 2026-05-13 08:11:00 +02:00
Lluciocc
9574b99f40
Merge branch 'BoredDevNL:main' into cmds 2026-05-13 08:06:03 +02:00
Lluciocc
2580700ff9
Update time.c 2026-05-12 18:40:27 +02:00
Lluciocc
078ad437a5
Rename 'ptime' command to 'time' in help text 2026-05-12 18:33:08 +02:00
Lluciocc
c275da6145
Rename ptime to time 2026-05-12 18:32:36 +02:00
Lluciocc
0075493fba
Rename object files in Makefile to include 'libc_' prefix 2026-05-12 18:31:25 +02:00
Lluciocc
11593b1b23
Add ptime command help message 2026-05-12 09:06:05 +02:00
Lluciocc
70ab1837c2
Add ptime 2026-05-12 09:04:27 +02:00
Lluciocc
96ddced34c
Merge branch 'BoredDevNL:main' into cmds 2026-05-12 08:56:11 +02:00
Lluciocc
fe1ba182d9
Update with missing ; 2026-05-11 20:25:35 +02:00
Lluciocc
f94384e572
Add hexdump and ps command descriptions to help 2026-05-11 19:36:27 +02:00
Lluciocc
ba281ea3f3
Adding hexdump and ps.c 2026-05-11 19:32:26 +02:00
22 changed files with 1144 additions and 156 deletions

View file

@ -2,6 +2,8 @@
# This software is released under the GNU General Public License v3.0. See LICENSE file for details. # This software is released under the GNU General Public License v3.0. See LICENSE file for details.
# This header needs to maintain in any file it is present in, as per the GPL license terms. # This header needs to maintain in any file it is present in, as per the GPL license terms.
export MAKEFLAGS += -j4
CC = x86_64-elf-gcc CC = x86_64-elf-gcc
LD = x86_64-elf-ld LD = x86_64-elf-ld
NASM = nasm NASM = nasm

View file

@ -1,35 +1,87 @@
<div align="center"> <div align="center">
<h1>ACPI</h1> <h1>ACPI</h1>
<p><em>ACPI Power Interface</em></p> <p><em>Power management and hardware enumeration via the Advanced Configuration and Power Interface.</em></p>
</div> </div>
ACPI Subsystem ---
BoredOS implements an ACPI subsystem which manages power state transitions. BoredOS implements an ACPI subsystem that handles power state transitions, hardware discovery, and I2C device enumeration from the firmware tables. The implementation lives in `src/drivers/ACPI/`.
The implementation lives
in `src/acpi/` ## Table Discovery
At boot, the kernel requests the RSDP (Root System Description Pointer) from Limine. If the pointer is absent or fails its checksum, the kernel panics - this is a hard requirement, not a graceful fallback.
From the RSDP, the kernel determines which top-level descriptor table to use:
- **XSDT** (64-bit pointers) - used when RSDP revision ≥ 2 and a valid `xsdt_address` is present.
- **RSDT** (32-bit pointers) - fallback for older firmware.
This selection is handled transparently by `acpi_get_sdt()`, which walks whichever table is available and returns the first SDT matching a given 4-char signature.
> [!NOTE]
> The DSDT is a special case - it is not listed in the XSDT/RSDT. It is instead pointed to directly by the `dsdt` field in the FADT. Use `acpi_get_dsdt()` to retrieve it.
--- ---
### Startup and Table Discovery ## FADT and ACPI Enable
The first thing that ACPI does at boot time is to locate the RSDP.This can simply be requested fromlimine. The FADT (Fixed ACPI Description Table, signature `"FACP"`) is located first. It contains the port addresses needed for power management and the SMI command port used to hand off hardware control from firmware to the OS.
If the pointer is not present or the pointer is not valid we panic!It is a hardware requirement
If this is not present then we also check a checksum if that fails then the RSDP is also not valid and we panic If `smi_cmd` and `acpi_enable` are both non-zero, the kernel writes the enable byte to the SMI command port and spins on `PM1a_CNT` bit 0 until the hardware acknowledges. A timeout here is a fatal error.
XSDT vs RSDT Fallback
When the RSDP specifies aRevision of 2 or greater, and we have a valid XSDT Address then we should try and use the XSDT instead,otherwise we use the RSDT.This is automatically abstracted by `acpi_get_sdt()`
--- ---
### Shutdown and Power Off ## MADT and Interrupt Routing
This works by writing an appropriate value to PM1 Control Block register, and will prompt hardware to move into S5 power state. The MADT (Multiple APIC Description Table, signature `"APIC"`) is parsed for Interrupt Source Override entries (type 2). These describe cases where a legacy ISA IRQ is wired to a different GSI on the I/O APIC - for example, IRQ 0 (PIT) may be redirected to GSI 2 on some platforms.
### Performing the power off Two functions expose this mapping to the rest of the kernel:
Once the sleep types are identified and so on, this will continue as follows
On hardware that actually obeys ACPI standards this process should only perform step 1, and your machine should be turned off. To try and handle virtual machines or emulators that are known to fail at step 1,
we send the virtual machine interrupts second in case writing bytes makes our hardware think it is acting up and we do not wish to cause an unnecessary interrupt - `acpi_irq_to_gsi(irq)` - returns the GSI for a given IRQ, or the IRQ itself if no override exists.
- `acpi_irq_flags(irq)` - returns the polarity and trigger mode flags for the override.
---
## AML Parsing
The DSDT and any SSDTs present in the firmware contain AML (ACPI Machine Language) bytecode describing the hardware namespace. BoredOS does not implement a full AML interpreter - instead `src/drivers/ACPI/acpi_aml.c` implements a targeted byte-stream walker sufficient for I2C device enumeration.
The walker handles:
- **`_HID`** - device identification, either as an inline string or a packed EISAID integer.
- **`_CRS`** - current resource settings; specifically scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address, bus speed, and addressing mode.
- **`_DSM`** - device-specific method; scans the raw method body for the I2C-HID GUID (`3cdff6f7-4267-4555-ad05-b30a3d8938de`) and extracts the HID descriptor register address from the static return package.
- **Power states** - records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags.
> [!IMPORTANT]
> `_CRS` and `_DSM` must be statically defined `Name()` objects for the walker to read them. Dynamically computed resources via AML method evaluation are not supported.
---
## I2C Enumeration
`acpi_i2c_enumerate()` is called at the end of `acpi_init()`. It walks the DSDT via `acpi_get_dsdt()` followed by all SSDTs found in the XSDT/RSDT, feeding each into `aml_walk_table()`.
Devices are emitted into a flat table when both a `_HID` and a valid `I2cSerialBusV2` resource (non-zero `connection_speed`) are found. The result is accessible via:
- `acpi_i2c_count()` - number of enumerated devices.
- `acpi_i2c_get(index)` - pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, speed, DSM result, and power flags.
---
## Shutdown
Performing a clean S5 (soft power-off) requires writing the correct `SLP_TYP` value to the PM1 Control Block. This value is firmware-specific and must be read from the `_S5_` object in the DSDT.
`aml_parse_s5()` scans the DSDT AML for the `_S5_` name and extracts `SLP_TYPa` and `SLP_TYPb`, pre-shifted to bit position 10 ready for `outw`. If the parse fails, the kernel falls back to `SLP_TYP = 5`, which works on the majority of hardware.
### Shutdown sequence
On compliant hardware, step 1 is all that runs and the machine powers off immediately. The remaining steps target known virtual machine implementations that don't respond to standard ACPI.
1. Write `SLP_TYPa | SLP_EN` to `PM1a_CNT_BLK` (and `PM1b_CNT_BLK` if present).
2. Write known virtualizer-specific magic values - QEMU (`0x604`), VirtualBox (`0x4004`), Bochs (`0xB004`), Cloud Hypervisor (`0x600`) - to trigger their respective shutdown paths.
3. Disable interrupts and halt indefinitely as a last resort.
> [!NOTE]
> The virtualizer ports are sent after the ACPI write deliberately. On real hardware, sending those port values is harmless, but the ordering avoids any ambiguity about what triggered the power-off.

View file

@ -132,6 +132,7 @@ Below are some of the most used commands available in `/bin`:
| `lsblk` | List block devices and partitions. | | `lsblk` | List block devices and partitions. |
| `du` | Report disk usage for files and directories. | | `du` | Report disk usage for files and directories. |
| `sysfetch` | Display system information and BoredOS branding. | | `sysfetch` | Display system information and BoredOS branding. |
| `time` | Measure command execution time. |
--- ---

View file

@ -10,4 +10,13 @@ void kconsole_putc(char c);
void kconsole_write(const char *s); void kconsole_write(const char *s);
void kconsole_set_active(bool active); void kconsole_set_active(bool active);
void serial_write(const char *str);
void serial_write_num_locked(uint32_t n);
void serial_write_num(uint32_t n);
void serial_write_hex_locked(uint64_t n);
void serial_write_hex(uint64_t n);
void log_ok(const char *msg);
void log_fail(const char *msg);
#endif // KCONSOLE_H #endif // KCONSOLE_H

View file

@ -5,7 +5,7 @@
#include "wm.h" #include "wm.h"
#include "io.h" #include "io.h"
#include "../drivers/acpi.h" #include "../drivers/ACPI/acpi.h"
void memset(void *dest, int val, size_t len) { void memset(void *dest, int val, size_t len) {
unsigned char *ptr = (unsigned char *)dest; unsigned char *ptr = (unsigned char *)dest;
@ -75,6 +75,20 @@ void strcpy(char *dest, const char *src) {
*dest = 0; *dest = 0;
} }
char *strncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n && src[i] != '\0'; i++) {
dest[i] = src[i];
}
for (; i < n; i++) {
dest[i] = '\0';
}
return dest;
}
int atoi(const char *str) { int atoi(const char *str) {
int res = 0; int res = 0;
int sign = 1; int sign = 1;

View file

@ -17,6 +17,7 @@ size_t strlen(const char *str);
int strcmp(const char *s1, const char *s2); int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n); int strncmp(const char *s1, const char *s2, size_t n);
void strcpy(char *dest, const char *src); void strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
int atoi(const char *str); int atoi(const char *str);
void itoa(int n, char *buf); void itoa(int n, char *buf);
void itoa_hex(uint64_t n, char *buf); void itoa_hex(uint64_t n, char *buf);

View file

@ -35,7 +35,7 @@
#include "sys/bootfs_state.h" #include "sys/bootfs_state.h"
#include "input/keymap.h" #include "input/keymap.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "../drivers/acpi.h" #include "../drivers/ACPI/acpi.h"
extern void sysfs_init_subsystems(void); extern void sysfs_init_subsystems(void);
@ -134,7 +134,7 @@ void serial_write(const char *str) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_num_locked(uint32_t n) { void serial_write_num_locked(uint32_t n) {
if (n >= 10) serial_write_num_locked(n / 10); if (n >= 10) serial_write_num_locked(n / 10);
char c = '0' + (n % 10); char c = '0' + (n % 10);
while ((inb(0x3F8 + 5) & 0x20) == 0); while ((inb(0x3F8 + 5) & 0x20) == 0);
@ -148,7 +148,7 @@ void serial_write_num(uint32_t n) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_hex_locked(uint64_t n) { void serial_write_hex_locked(uint64_t n) {
char *hex = "0123456789ABCDEF"; char *hex = "0123456789ABCDEF";
if (n >= 16) serial_write_hex_locked(n / 16); if (n >= 16) serial_write_hex_locked(n / 16);
char c = hex[n % 16]; char c = hex[n % 16];

View file

@ -1,7 +1,12 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include "acpi_structures.h" #include "acpi_structures.h"
#include "../I2C/acpi_i2c.h"
#include "acpi.h" #include "acpi.h"
#include "../sys/idt.h" #include "../sys/idt.h"
#include "../core/limine.h" #include "../core/limine.h"
@ -36,7 +41,7 @@ static int acpi_checksum(void *ptr, size_t len) {
return sum == 0; return sum == 0;
} }
static void *acpi_get_rsdp(void){ struct acpi_rsdp *acpi_get_rsdp(void){
extern volatile struct limine_rsdp_request acpi_rsdp_request; extern volatile struct limine_rsdp_request acpi_rsdp_request;
if (!acpi_rsdp_request.response) if (!acpi_rsdp_request.response)
@ -47,7 +52,7 @@ static void *acpi_get_rsdp(void){
return acpi_rsdp_request.response->address; return acpi_rsdp_request.response->address;
} }
static struct acpi_sdt *acpi_get_sdt(const char signature[4]) { struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) { if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) {
struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address); struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address);
if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) { if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) {
@ -81,8 +86,8 @@ static struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
return NULL; return NULL;
} }
static uint16_t SLP_TYPa = 0; uint16_t SLP_TYPa = 0;
static uint16_t SLP_TYPb = 0; uint16_t SLP_TYPb = 0;
void acpi_parse_s5(void) { void acpi_parse_s5(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return; if (!acpi_fadt || !acpi_fadt->dsdt) return;
@ -199,9 +204,12 @@ int acpi_init(void){
ptr += h->length; ptr += h->length;
} }
acpi_i2c_enumerate();
return 0; return 0;
} }
uint32_t acpi_irq_to_gsi(uint32_t irq) { uint32_t acpi_irq_to_gsi(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) { for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq) if (iso_table[i].source == irq)
@ -218,3 +226,8 @@ uint16_t acpi_irq_flags(uint32_t irq) {
return 0; return 0;
} }
struct acpi_sdt *acpi_get_dsdt(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return NULL;
return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt);
}

35
src/drivers/ACPI/acpi.h Normal file
View file

@ -0,0 +1,35 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef ACPI_H
#define ACPI_H
//power stuff
#define PM1A_CNT fadt->pm1a_cnt_blk
#define PM1B_CNT fadt->pm1b_cnt_blk
#define ACPI_S5 0x5
#define SLP_EN (1 << 13)
#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN)
extern uint16_t SLP_TYPa;
extern uint16_t SLP_TYPb;
int acpi_init(void);
uint32_t acpi_irq_to_gsi(uint32_t irq);
uint16_t acpi_irq_flags(uint32_t irq);
struct acpi_rsdp *acpi_get_rsdp(void);
struct acpi_sdt *acpi_get_sdt(const char signature[4]);
struct acpi_sdt *acpi_get_dsdt(void);
void acpi_parse_s5(void);
// ACPI Functionality Implementations
// - do reboot properly
__attribute__((noreturn)) void acpi_shutdown(void);
#endif

417
src/drivers/ACPI/acpi_aml.c Normal file
View file

@ -0,0 +1,417 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "acpi_aml.h"
#include "kutils.h"
#include "kconsole.h"
#include <stdint.h>
#include <stddef.h>
/// @brief Decode AML PkgLength field, advance past it
/// @param pp in/out byte pointer, advanced past the field on return
/// @param end hard bound; returns 0 if field exceeds it
/// @return total package length including the PkgLength bytes, 0 on error
static size_t decode_pkglen(const uint8_t **pp, const uint8_t *end) {
if (*pp >= end) return 0;
uint8_t b0 = *(*pp)++;
uint8_t extra = b0 >> 6;
size_t len;
if (extra == 0) {
len = b0 & 0x3F; // 1-byte form: bits[5:0]
} else {
len = b0 & 0x0F; // multi-byte form: bits[3:0] are low nibble
if (*pp + extra > end) return 0;
for (uint8_t i = 0; i < extra; i++)
len |= (size_t)(*(*pp)++) << (4 + 8 * i);
}
return len;
}
/// @brief Decode the last NameSeg of an AML NameString into out
/// @param pp in/out byte pointer, advanced past the full name on return
/// @param end hard bound
/// @param out receives null-terminated 4-char NameSeg
static void decode_nameseg(const uint8_t **pp, const uint8_t *end,
char out[AML_NAME_LEN]) {
out[0] = '\0';
if (*pp >= end) return;
while (*pp < end && (**pp == '\\' || **pp == '^')) (*pp)++;
if (*pp >= end) return;
uint8_t lead = **pp;
if (lead == 0x00) {
(*pp)++;
return;
} else if (lead == 0x2E) { // DualNamePath two 4-char segs
(*pp)++;
if (*pp + 8 > end) return;
(*pp) += 4;
for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i];
out[4] = '\0';
(*pp) += 4;
} else if (lead == 0x2F) { // MultiNamePath count + N segs
(*pp)++;
if (*pp >= end) return;
uint8_t count = *(*pp)++;
if (*pp + (size_t)count * 4 > end) return;
const uint8_t *last = *pp + (count - 1) * 4;
for (int i = 0; i < 4; i++) out[i] = (char)last[i];
out[4] = '\0';
*pp += (size_t)count * 4;
} else { // plain 4-char NameSeg
if (*pp + 4 > end) return;
for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i];
out[4] = '\0';
(*pp) += 4;
}
}
/// @brief Decode any AML integer data object (Zero, One, Byte, Word, DWord, QWord)
/// @param pp in/out byte pointer, advanced past the object on return
/// @param end hard bound
/// @return decoded integer value, 0 on unknown opcode (pointer not advanced)
static uint64_t decode_integer(const uint8_t **pp, const uint8_t *end) {
if (*pp >= end) return 0;
uint8_t op = *(*pp)++;
switch (op) {
case AML_ZERO_OP: return 0;
case AML_ONE_OP: return 1;
case AML_BYTE_PREFIX:
if (*pp + 1 > end) return 0;
return *(*pp)++;
case AML_WORD_PREFIX: {
if (*pp + 2 > end) return 0;
uint16_t v = (uint16_t)((*pp)[0]) | ((uint16_t)((*pp)[1]) << 8);
*pp += 2; return v;
}
case AML_DWORD_PREFIX: {
if (*pp + 4 > end) return 0;
uint32_t v = (uint32_t)((*pp)[0])
| ((uint32_t)((*pp)[1]) << 8)
| ((uint32_t)((*pp)[2]) << 16)
| ((uint32_t)((*pp)[3]) << 24);
*pp += 4; return v;
}
case AML_QWORD_PREFIX: {
if (*pp + 8 > end) return 0;
uint64_t v = 0;
for (int i = 0; i < 8; i++) v |= (uint64_t)((*pp)[i]) << (8 * i);
*pp += 8; return v;
}
default:
(*pp)--;
return 0;
}
}
/// @brief Convert 32-bit packed EISAID value to ASCII HID string (e.g. "PNP0C0E")
/// @param id EISAID as decoded by decode_integer (little-endian from AML)
/// @param out receives null-terminated 8-char string
static void eisaid_to_str(uint32_t id, char out[AML_HID_LEN]) {
uint32_t v = ((id & 0x000000FF) << 24)
| ((id & 0x0000FF00) << 8)
| ((id & 0x00FF0000) >> 8)
| ((id & 0xFF000000) >> 24);
out[0] = (char)('@' + ((v >> 26) & 0x1F));
out[1] = (char)('@' + ((v >> 21) & 0x1F));
out[2] = (char)('@' + ((v >> 16) & 0x1F));
static const char hex[] = "0123456789ABCDEF";
out[3] = hex[(v >> 12) & 0xF];
out[4] = hex[(v >> 8) & 0xF];
out[5] = hex[(v >> 4) & 0xF];
out[6] = hex[(v >> 0) & 0xF];
out[7] = '\0';
}
/// @brief Parse _HID data object (string or EISAID DWord) into dev->hid
/// @param pp in/out byte pointer positioned at the data object
/// @param end hard bound
/// @param dev target device record
static void parse_hid(const uint8_t **pp, const uint8_t *end,
aml_i2c_dev_t *dev) {
if (*pp >= end) return;
if (**pp == AML_STRING_PREFIX) {
(*pp)++;
const char *s = (const char *)*pp;
strncpy(dev->hid, s, AML_HID_LEN - 1);
dev->hid[AML_HID_LEN - 1] = '\0';
while (*pp < end && **pp) (*pp)++;
if (*pp < end) (*pp)++;
} else if (**pp == AML_DWORD_PREFIX) {
uint32_t id = (uint32_t)decode_integer(pp, end);
eisaid_to_str(id, dev->hid);
}
}
/// @brief Parse _CRS Buffer for an I2cSerialBusV2 descriptor, fill dev CRS fields
/// @param pp in/out byte pointer positioned at BufferOp
/// @param end hard bound
/// @param dev receives slave_address, speed_hz, ten_bit_addr on success
static void parse_crs(const uint8_t **pp, const uint8_t *end,
aml_i2c_dev_t *dev) {
if (*pp >= end || **pp != AML_BUFFER_OP) return;
(*pp)++;
const uint8_t *pkg_start = *pp;
size_t pkglen = decode_pkglen(pp, end);
if (!pkglen) return;
const uint8_t *buf_end = pkg_start + pkglen;
if (buf_end > end) buf_end = end;
decode_integer(pp, buf_end); // skip BufferSize
const uint8_t *res = *pp;
while (res < buf_end) {
uint8_t tag = *res;
if (tag == ACPI_RESOURCE_END_TAG) break;
if (tag == ACPI_LARGE_I2C_SERIAL_BUS) {
if (res + sizeof(aml_i2c_resource_t) > buf_end) break;
const aml_i2c_resource_t *r = (const aml_i2c_resource_t *)res;
if (r->serial_bus_type == ACPI_I2C_SERIAL_BUS_TYPE) {
dev->slave_address = r->slave_address;
dev->speed_hz = r->connection_speed;
dev->ten_bit_addr = (r->type_specific_flags & 0x01) ? 1 : 0;
}
res += sizeof(uint8_t) + sizeof(uint16_t) + r->length;
continue;
}
if (tag & 0x80) {
if (res + 3 > buf_end) break;
uint16_t len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8);
res += 3 + len;
} else {
res += 1 + (tag & 0x07);
}
}
*pp = buf_end;
}
static void record_power_state(const char name[AML_NAME_LEN],
aml_i2c_dev_t *dev) {
if (memcmp(name, "_PS0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS0;
else if (memcmp(name, "_PS3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS3;
else if (memcmp(name, "_PR0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR0;
else if (memcmp(name, "_PR3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR3;
}
/// @brief Best-effort skip of one AML data object to keep the scan on track
/// @param pp in/out byte pointer, advanced past the object on return
/// @param end hard bound
static void skip_object(const uint8_t **pp, const uint8_t *end) {
if (*pp >= end) return;
uint8_t op = **pp;
switch (op) {
case AML_ZERO_OP:
case AML_ONE_OP:
(*pp)++; return;
case AML_BYTE_PREFIX: (*pp) += 2; return;
case AML_WORD_PREFIX: (*pp) += 3; return;
case AML_DWORD_PREFIX: (*pp) += 5; return;
case AML_QWORD_PREFIX: (*pp) += 9; return;
case AML_STRING_PREFIX:
(*pp)++;
while (*pp < end && **pp) (*pp)++;
if (*pp < end) (*pp)++;
return;
case AML_BUFFER_OP:
case AML_PACKAGE_OP: {
(*pp)++;
const uint8_t *start = *pp;
size_t len = decode_pkglen(pp, end);
if (len) *pp = start + len;
return;
}
default:
(*pp)++;
}
}
/// @brief Scan one Device() scope body for _HID, _CRS, _DSM, and power objects
/// @param p first byte of the scope body (after NameSeg)
/// @param end one past last byte of the scope
/// @param dev device record to populate
static void scan_device_scope(const uint8_t *p, const uint8_t *end,
aml_i2c_dev_t *dev) {
while (p < end) {
uint8_t op = *p;
if (op == AML_NAME_OP) {
p++;
char seg[AML_NAME_LEN];
decode_nameseg(&p, end, seg);
if (memcmp(seg, "_HID", 4) == 0) parse_hid(&p, end, dev);
else if (memcmp(seg, "_CRS", 4) == 0) parse_crs(&p, end, dev);
else { record_power_state(seg, dev); skip_object(&p, end); }
continue;
}
if (op == AML_METHOD_OP) {
p++;
const uint8_t *method_start = p;
size_t pkglen = decode_pkglen(&p, end);
if (!pkglen) break;
const uint8_t *method_end = method_start + pkglen;
if (method_end > end) method_end = end;
char seg[AML_NAME_LEN];
const uint8_t *name_ptr = p;
decode_nameseg(&name_ptr, method_end, seg);
if (memcmp(seg, "_DSM", 4) == 0) {
const uint8_t *body = name_ptr + 1; // skip MethodFlags we dont care abt it rn
const uint8_t *scan = body;
const uint8_t *guid = (const uint8_t *)ACPI_I2C_HID_DSM_GUID;
while (scan + 16 < method_end) {
if (memcmp(scan, guid, 16) != 0) { scan++; continue; }
scan += 16;
while (scan + 4 < method_end) {
if (*scan != AML_RETURN_OP) { scan++; continue; }
scan++;
if (*scan != AML_PACKAGE_OP) { continue; }
scan++;
const uint8_t *ps = scan;
size_t pl = decode_pkglen(&scan, method_end);
if (!pl) break;
const uint8_t *pe = ps + pl;
if (pe > method_end || scan >= pe) break;
uint8_t nelem = *scan++;
if (nelem < 1) break;
uint64_t val = decode_integer(&scan, pe);
if (val <= 0xFFFF) {
dev->hid_desc_addr = (uint16_t)val;
dev->has_dsm = 1;
}
goto method_done;
}
break;
}
} else {
record_power_state(seg, dev);
}
method_done:
p = method_end;
continue;
}
// skip nested Device or Scope without recursing
if (op == AML_EXTOP_PREFIX && p + 1 < end && *(p + 1) == AML_DEVICE_OP) {
p += 2;
const uint8_t *s = p;
size_t l = decode_pkglen(&p, end);
if (l) p = s + l; else break;
continue;
}
if (op == AML_SCOPE_OP) {
p++;
const uint8_t *s = p;
size_t l = decode_pkglen(&p, end);
if (l) p = s + l; else break;
continue;
}
p++;
}
}
void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx) {
if (!aml || !len || !ctx || !ctx->devices) return;
const uint8_t *p = aml;
const uint8_t *end = aml + len;
while (p + 2 < end) {
if (p[0] != AML_EXTOP_PREFIX || p[1] != AML_DEVICE_OP) { p++; continue; }
p += 2;
const uint8_t *scope_start = p;
size_t pkglen = decode_pkglen(&p, end);
if (!pkglen) continue;
const uint8_t *scope_end = scope_start + pkglen;
if (scope_end > end) scope_end = end;
char dev_name[AML_NAME_LEN];
decode_nameseg(&p, scope_end, dev_name);
if (ctx->count >= ctx->capacity) break;
aml_i2c_dev_t *dev = &ctx->devices[ctx->count];
memset(dev, 0, sizeof(aml_i2c_dev_t));
memcpy(dev->name, dev_name, AML_NAME_LEN);
scan_device_scope(p, scope_end, dev);
scan_device_scope(p, scope_end, dev);
if (dev->hid[0] && dev->speed_hz) {
dev->valid = 1;
ctx->count++;
}
p = scope_end;
}
}
int aml_parse_s5(const uint8_t *aml, size_t len,
uint16_t *slp_typa, uint16_t *slp_typb) {
if (!aml || !len || !slp_typa || !slp_typb) return 0;
const uint8_t *end = aml + len;
const uint8_t *p = aml;
while (p + 4 < end) {
if (p[0] != '_' || p[1] != 'S' || p[2] != '5' || p[3] != '_') {
p++; continue;
}
p += 4;
// scan ahead up to 4 bytes for PackageOp; on diff firmware layouts YMMV
int found_pkg = 0;
for (int skip = 0; skip < 4 && p + skip < end; skip++) {
if (p[skip] == AML_PACKAGE_OP) {
p += skip + 1;
found_pkg = 1;
break;
}
}
if (!found_pkg) continue;
const uint8_t *pkg_start = p;
size_t pkglen = decode_pkglen(&p, end);
if (!pkglen) continue;
const uint8_t *pkg_end = pkg_start + pkglen;
if (pkg_end > end) pkg_end = end;
if (p >= pkg_end) continue;
uint8_t num = *p++;
if (num < 2) continue;
uint64_t typa = decode_integer(&p, pkg_end);
uint64_t typb = decode_integer(&p, pkg_end);
*slp_typa = (uint16_t)((typa & 0x7) << 10);
*slp_typb = (uint16_t)((typb & 0x7) << 10);
return 1;
}
return 0;
}

View file

@ -0,0 +1,96 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef ACPI_AML_H
#define ACPI_AML_H
#include <stdint.h>
#include <stddef.h>
#define AML_ZERO_OP 0x00
#define AML_ONE_OP 0x01
#define AML_NAME_OP 0x08
#define AML_BYTE_PREFIX 0x0A
#define AML_WORD_PREFIX 0x0B
#define AML_DWORD_PREFIX 0x0C
#define AML_STRING_PREFIX 0x0D
#define AML_QWORD_PREFIX 0x0E
#define AML_SCOPE_OP 0x10
#define AML_BUFFER_OP 0x11
#define AML_PACKAGE_OP 0x12
#define AML_METHOD_OP 0x14
#define AML_EXTOP_PREFIX 0x5B
#define AML_DEVICE_OP 0x82 // always preceded by 0x5B
#define AML_RETURN_OP 0xA4
// ACPI resource descriptor tags
#define ACPI_RESOURCE_END_TAG 0x79
#define ACPI_LARGE_ITEM 0x80
#define ACPI_LARGE_I2C_SERIAL_BUS 0x8E
#define ACPI_I2C_SERIAL_BUS_TYPE 0x01
// I2cSerialBusV2 resource descriptor
typedef struct __attribute__((packed)) {
uint8_t tag;
uint16_t length;
uint8_t revision_id;
uint8_t resource_source_index;
uint8_t serial_bus_type;
uint8_t general_flags;
uint16_t type_specific_flags;
uint8_t type_specific_revision_id;
uint16_t type_data_length;
uint32_t connection_speed;
uint16_t slave_address;
} aml_i2c_resource_t;
/*
GUID: 3cdff6f7-4267-4555-ad05-b30a3d8938de, mixed-endian AML layout
sorry i know its a magic number but tis the way of things
*/
#define ACPI_I2C_HID_DSM_GUID \
"\xf7\xf6\xdf\x3c\x67\x42\x55\x45\xad\x05\xb3\x0a\x3d\x89\x38\xde"
#define AML_PWR_HAS_PS0 (1 << 0)
#define AML_PWR_HAS_PS3 (1 << 1)
#define AML_PWR_HAS_PR0 (1 << 2)
#define AML_PWR_HAS_PR3 (1 << 3)
#define AML_HID_LEN 9
#define AML_NAME_LEN 5
typedef struct {
char name[AML_NAME_LEN];
char hid[AML_HID_LEN];
uint16_t slave_address;
uint32_t speed_hz;
uint8_t ten_bit_addr;
uint16_t hid_desc_addr;
uint8_t has_dsm;
uint8_t power_flags;
uint8_t valid;
} aml_i2c_dev_t;
typedef struct {
aml_i2c_dev_t *devices;
size_t capacity;
size_t count;
} aml_walk_ctx_t;
/// @brief Walk one DSDT or SSDT AML region, emitting I2C device records
/// @param aml first AML byte (SDT base + 36)
/// @param len byte length of the AML region
/// @param ctx output context; devices array must be pre-allocated
void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx);
/// @brief Scan DSDT AML for _S5_ and extract SLP_TYPa/b for S5 power-off
/// @param aml first AML byte (DSDT base + 36)
/// @param len byte length of the AML region
/// @param slp_typa out - SLP_TYPa pre-shifted to PM1_CNT bit 10
/// @param slp_typb out - SLP_TYPb pre-shifted to PM1_CNT bit 10
/// @return 1 if _S5_ found, 0 if not found (caller should use fallback)
int aml_parse_s5(const uint8_t *aml, size_t len,
uint16_t *slp_typa, uint16_t *slp_typb);
#endif

View file

@ -1,3 +1,7 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef ACPI_STRUCTURES_H #ifndef ACPI_STRUCTURES_H
#define ACPI_STRUCTURES_H #define ACPI_STRUCTURES_H

115
src/drivers/I2C/acpi_i2c.c Normal file
View file

@ -0,0 +1,115 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "acpi_i2c.h"
#include "../ACPI/acpi.h"
#include "../ACPI/acpi_aml.h"
#include "../ACPI/acpi_structures.h"
#include "../core/kconsole.h"
#include "../core/platform.h"
static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES];
static size_t i2c_device_count = 0;
// ACPI SDT header is 36 bytes AML starts concurrently, nice and convenient
#define ACPI_SDT_HEADER_LEN 36
static void walk_sdt(struct acpi_sdt *sdt, aml_walk_ctx_t *ctx) {
if (!sdt) return;
if (sdt->length <= ACPI_SDT_HEADER_LEN) return;
const uint8_t *aml = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN;
size_t len = sdt->length - ACPI_SDT_HEADER_LEN;
aml_walk_table(aml, len, ctx);
}
static void walk_all_ssdts(aml_walk_ctx_t *ctx) {
struct acpi_rsdp *rsdp = acpi_get_rsdp();
if (!rsdp) return;
if (rsdp->revision >= 2 && rsdp->xsdt_address) {
struct acpi_xsdt *xsdt = (struct acpi_xsdt *)p2v(rsdp->xsdt_address);
size_t entries = (xsdt->header.length - sizeof(struct acpi_sdt)) / 8;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]);
if (!sdt) continue;
if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0)
walk_sdt(sdt, ctx);
}
return;
}
// RSDT fallback
if (!rsdp->rsdt_address) return;
struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(rsdp->rsdt_address);
uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt));
size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]);
if (!sdt) continue;
if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0)
walk_sdt(sdt, ctx);
}
}
int acpi_i2c_enumerate(void) {
i2c_device_count = 0;
aml_walk_ctx_t ctx = {
.devices = i2c_devices,
.capacity = ACPI_I2C_MAX_DEVICES,
.count = 0,
};
// Walk DSDT - pointed to by FADT, not listed in XSDT/RSDT
struct acpi_sdt *dsdt = acpi_get_dsdt();
if (dsdt) {
walk_sdt(dsdt, &ctx);
} else {
serial_write("[acpi_i2c] warning: DSDT not found\n");
}
// Walk all SSDTs
walk_all_ssdts(&ctx);
i2c_device_count = ctx.count;
for (size_t i = 0; i < i2c_device_count; i++) {
const aml_i2c_dev_t *d = &i2c_devices[i];
serial_write("[acpi_i2c] ");
serial_write(d->name);
serial_write(" hid=");
serial_write(d->hid);
serial_write(" addr=0x");
serial_write_hex(d->slave_address);
serial_write(" speed=");
serial_write_num(d->speed_hz);
serial_write(" Hz");
if (d->ten_bit_addr) serial_write(" 10-bit");
if (d->power_flags & (AML_PWR_HAS_PS0 | AML_PWR_HAS_PS3)) serial_write(" PS0/3");
if (d->power_flags & (AML_PWR_HAS_PR0 | AML_PWR_HAS_PR3)) serial_write(" PR0/3");
if (d->has_dsm) {
serial_write(" hid_desc_reg=0x");
serial_write_hex(d->hid_desc_addr);
}
serial_write("\n");
}
if (i2c_device_count != 0) {
log_ok("I2C enumeration complete");
} else {
log_fail("No I2C devices found in ACPI tables");
}
return (int)i2c_device_count;
}
size_t acpi_i2c_count(void) {
return i2c_device_count;
}
const aml_i2c_dev_t *acpi_i2c_get(size_t index) {
if (index >= i2c_device_count) return NULL;
return &i2c_devices[index];
}

View file

@ -0,0 +1,20 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef ACPI_I2C_H
#define ACPI_I2C_H
#include <stdint.h>
#include <stddef.h>
#include "../ACPI/acpi_aml.h"
#define ACPI_I2C_MAX_DEVICES 32
int acpi_i2c_enumerate(void);
size_t acpi_i2c_count(void);
const aml_i2c_dev_t *acpi_i2c_get(size_t index);
#endif

View file

@ -1,25 +0,0 @@
#ifndef ACPI_H
#define ACPI_H
int acpi_init(void);
__attribute__((noreturn)) void acpi_shutdown(void);
__attribute__((noreturn)) void acpi_reboot(void);
uint32_t acpi_irq_to_gsi(uint32_t irq);
uint16_t acpi_irq_flags(uint32_t irq);
//power stuff
#define PM1A_CNT fadt->pm1a_cnt_blk
#define PM1B_CNT fadt->pm1b_cnt_blk
#define ACPI_S5 0x5
#define SLP_EN (1 << 13)
#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN)
void acpi_parse_s5(void);
__attribute__((noreturn)) void acpi_shutdown(void);
#endif

View file

@ -20,7 +20,7 @@ $(if $(filter doom,$1),$(APP_METADATA_SOURCE_DOOM),$(if $(filter lua,$1),$(APP_M
endef endef
LIBC_SOURCES = $(wildcard libc/*.c) LIBC_SOURCES = $(wildcard libc/*.c)
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o $(BIN_DIR)/stb_image.o LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/libc_%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o $(BIN_DIR)/stb_image.o
VPATH = cli gui sys games libc net cli/third_party VPATH = cli gui sys games libc net cli/third_party
vpath %.c cli gui sys games libc net cli/third_party vpath %.c cli gui sys games libc net cli/third_party
@ -46,7 +46,7 @@ $(BIN_DIR):
$(BIN_DIR)/crt0.o: crt0.asm $(BIN_DIR)/crt0.o: crt0.asm
$(AS) -f elf64 $< -o $@ $(AS) -f elf64 $< -o $@
$(BIN_DIR)/%.o: libc/%.c | $(BIN_DIR) $(BIN_DIR)/libc_%.o: libc/%.c | $(BIN_DIR)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/libwidget.o: ../wm/libwidget.c | $(BIN_DIR) $(BIN_DIR)/libwidget.o: ../wm/libwidget.c | $(BIN_DIR)

View file

@ -6,6 +6,7 @@
#include <unistd.h> #include <unistd.h>
#include <syscall.h> #include <syscall.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h>
#include "utf-8.h" #include "utf-8.h"
#define MAX_LINE 512 #define MAX_LINE 512
@ -39,6 +40,8 @@ typedef struct {
static bsh_config_t g_cfg; static bsh_config_t g_cfg;
static char g_paths[MAX_PATHS][MAX_PATH_LEN]; static char g_paths[MAX_PATHS][MAX_PATH_LEN];
static int g_path_count = 0; static int g_path_count = 0;
static char g_resolved_command_path[256];
static int g_resolve_status = -1;
static char g_history[MAX_HISTORY][MAX_LINE]; static char g_history[MAX_HISTORY][MAX_LINE];
static int g_history_count = 0; static int g_history_count = 0;
@ -678,55 +681,138 @@ static bool is_elf_file(const char *path) {
return hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F'; return hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F';
} }
static int resolve_command(const char *cmd, char *out, int out_len) { static bool contains_slash(const char *cmd) {
if (!cmd || !cmd[0]) return -1; if (!cmd) return false;
bool has_slash = false;
for (int i = 0; cmd[i]; i++) { for (int i = 0; cmd[i]; i++) {
if (cmd[i] == '/') { has_slash = true; break; } if (cmd[i] == '/') return true;
}
return false;
}
static const char *env_get_value(char *const envp[], const char *name) {
int name_len;
if (!envp || !name || !name[0]) return NULL;
name_len = (int)strlen(name);
for (int i = 0; envp[i]; i++) {
if (starts_with(envp[i], name) && envp[i][name_len] == '=') {
return envp[i] + name_len + 1;
}
} }
if (has_slash) { return NULL;
if (sys_exists(cmd)) { }
if (!is_file_path(cmd)) return -2;
if (!is_elf_file(cmd)) return -3; static void build_path_candidate(char *out, int out_len, const char *dir, int dir_len, const char *cmd) {
str_copy(out, cmd, out_len); int pos = 0;
return 0;
} if (!out || out_len <= 0) return;
if (!ends_with(cmd, ".elf")) { out[0] = 0;
char temp[256]; if (!dir || !cmd) return;
str_copy(temp, cmd, sizeof(temp));
str_append(temp, ".elf", sizeof(temp)); for (int i = 0; i < dir_len && dir[i] && pos < out_len - 1; i++) {
if (sys_exists(temp)) { out[pos++] = dir[i];
if (!is_file_path(temp)) return -2; }
if (!is_elf_file(temp)) return -3; out[pos] = 0;
str_copy(out, temp, out_len);
return 0; if (pos > 0 && out[pos - 1] != '/' && pos < out_len - 1) {
out[pos++] = '/';
out[pos] = 0;
}
for (int i = 0; cmd[i] && pos < out_len - 1; i++) {
out[pos++] = cmd[i];
}
out[pos] = 0;
}
static int accept_command_candidate(const char *candidate) {
if (access(candidate, X_OK) != 0) return -1;
if (!is_file_path(candidate)) return -2;
if (!is_elf_file(candidate)) return -3;
str_copy(g_resolved_command_path, candidate, sizeof(g_resolved_command_path));
return 0;
}
static char *resolve_in_path_string(const char *cmd, const char *path_str) {
int first_error = -1;
int start = 0;
int i = 0;
if (!path_str || !path_str[0]) {
g_resolve_status = -1;
return NULL;
}
while (1) {
if (path_str[i] == ':' || path_str[i] == 0) {
int len = i - start;
if (len > 0) {
char candidate[256];
int res;
build_path_candidate(candidate, sizeof(candidate), path_str + start, len, cmd);
res = accept_command_candidate(candidate);
if (res == 0) {
g_resolve_status = 0;
return g_resolved_command_path;
}
if (res != -1 && first_error == -1) first_error = res;
if (!ends_with(cmd, ".elf")) {
str_append(candidate, ".elf", sizeof(candidate));
res = accept_command_candidate(candidate);
if (res == 0) {
g_resolve_status = 0;
return g_resolved_command_path;
}
if (res != -1 && first_error == -1) first_error = res;
}
} }
start = i + 1;
} }
return -1;
if (path_str[i] == 0) break;
i++;
} }
for (int i = 0; i < g_path_count; i++) { g_resolve_status = first_error;
char temp[256]; return NULL;
temp[0] = 0; }
str_copy(temp, g_paths[i], sizeof(temp));
if (temp[0] && temp[strlen(temp) - 1] != '/') str_append(temp, "/", sizeof(temp)); static char *resolve_command_path(const char *cmd, char *const envp[]) {
str_append(temp, cmd, sizeof(temp)); const char *path;
if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) { int res;
str_copy(out, temp, out_len);
return 0; g_resolved_command_path[0] = 0;
} g_resolve_status = -1;
if (!ends_with(cmd, ".elf")) {
str_append(temp, ".elf", sizeof(temp)); if (!cmd || !cmd[0]) return NULL;
if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) {
str_copy(out, temp, out_len); if (contains_slash(cmd)) {
return 0; res = accept_command_candidate(cmd);
} g_resolve_status = res;
} return (res == 0) ? g_resolved_command_path : NULL;
} }
return -1; path = env_get_value(envp, "PATH");
if (!path) path = g_cfg.path;
return resolve_in_path_string(cmd, path);
}
static int resolve_command(const char *cmd, char *out, int out_len) {
char *resolved = resolve_command_path(cmd, NULL);
if (!resolved) return g_resolve_status;
str_copy(out, resolved, out_len);
return 0;
} }
static void build_args_string(int argc, char *argv[], int start, char *out, int out_len) { static void build_args_string(int argc, char *argv[], int start, char *out, int out_len) {
@ -796,7 +882,7 @@ static int collect_command_matches(const char *prefix, char matches[][MAX_MATCH_
int count = 0; int count = 0;
const char *builtins[] = { const char *builtins[] = {
"cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm", "cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm",
"touch", "cp", "mv", "man", "alias", "unalias", ".", "exit" "touch", "cp", "mv", "man", "alias", "unalias", "time", ".", "exit"
}; };
for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) { for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) {
if (starts_with(builtins[i], prefix)) count = add_match_unique(matches, count, builtins[i]); if (starts_with(builtins[i], prefix)) count = add_match_unique(matches, count, builtins[i]);
@ -910,17 +996,34 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha
redraw_input(prompt_tmpl, line, len, len); redraw_input(prompt_tmpl, line, len, len);
} }
static int wait_for_pid(int pid) { static int wait_for_pid_status(int pid, int *status) {
int status = 0;
while (1) { while (1) {
int rc = sys_waitpid(pid, &status, 1); int child_status = 0;
if (rc == pid || rc < 0) break;
int rc = sys_waitpid(pid, &child_status, 1);
if (rc == pid) {
if (status) *status = child_status;
return 0;
}
if (rc < 0) return -1;
if (g_tty_id >= 0) { if (g_tty_id >= 0) {
int fg = sys_tty_get_fg(g_tty_id); int fg = sys_tty_get_fg(g_tty_id);
if (fg != pid) break; if (fg != pid) return -1;
} }
sleep(10); sleep(10);
} }
}
static int wait_for_pid(int pid) {
int status = 0;
if (wait_for_pid_status(pid, &status) != 0)
return -1;
return status; return status;
} }
@ -934,6 +1037,120 @@ static void cmd_clear(void) {
sys_write(1, "\x1b[2J\x1b[H", 7); sys_write(1, "\x1b[2J\x1b[H", 7);
} }
static void print_command_resolution_error(const char *who, const char *cmd, int res) {
set_color(g_color_error);
if (res == -2) {
printf("%s: is a directory: %s\n", who, cmd);
} else if (res == -3) {
printf("%s: not executable: %s\n", who, cmd);
} else {
printf("%s: command not found: %s\n", who, cmd);
}
reset_color();
}
static void builtin_time_usage(void) {
printf("Usage: time <command> [args...]\n");
printf("\n");
printf("Examples:\n");
printf(" time ls\n");
printf(" time hexdump file.txt\n");
printf(" time /bin/hexdump.elf file.txt\n");
}
// Reads the system uptime in milliseconds by parsing /proc/uptime
// before and after running the command, then calculating the difference.
static unsigned long long read_uptime_ms(void) {
char buf[64];
int fd;
int bytes;
int seconds;
fd = sys_open("/proc/uptime", "r");
if (fd < 0) return 0;
bytes = sys_read(fd, buf, sizeof(buf) - 1);
sys_close(fd);
if (bytes <= 0) return 0;
buf[bytes] = 0;
seconds = atoi(buf);
return (unsigned long long)seconds * 1000ULL;
}
static int builtin_time(int argc, char *argv[]) {
char *resolved;
char full_path[256];
char args_buf[256];
char cmdline[MAX_LINE];
unsigned long long start;
unsigned long long end;
unsigned long long elapsed;
int pid = -1;
int ret = -1;
if (argc < 2) {
builtin_time_usage();
return 1;
}
if (str_eq(argv[1], "-h") || str_eq(argv[1], "--help")) {
builtin_time_usage();
return 0;
}
resolved = resolve_command_path(argv[1], NULL);
if (!resolved) {
print_command_resolution_error("time", argv[1], g_resolve_status);
return 1;
}
str_copy(full_path, resolved, sizeof(full_path));
build_args_string(argc, argv, 2, args_buf, sizeof(args_buf));
str_copy(cmdline, full_path, sizeof(cmdline));
if (args_buf[0]) {
str_append(cmdline, " ", sizeof(cmdline));
str_append(cmdline, args_buf, sizeof(cmdline));
}
start = read_uptime_ms();
for (int attempt = 0; attempt < 5; attempt++) {
pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY, 0);
if (pid >= 0) break;
sleep(10);
}
if (pid >= 0) {
if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid);
if (wait_for_pid_status(pid, &ret) != 0) ret = -1;
if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0);
}
end = read_uptime_ms();
if (end >= start) elapsed = end - start;
else elapsed = 0;
printf("\n");
printf("Command: %s\n", cmdline);
printf("Exit code: %d\n", ret);
if (ret == -1) {
printf("Command failed with non-zero exit code, not reporting time.\n");
return ret;
}
printf("Elapsed: %llu ms\n", elapsed);
sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0);
return ret;
}
static int builtin_cd(int argc, char *argv[]) { static int builtin_cd(int argc, char *argv[]) {
const char *path = (argc > 1) ? argv[1] : "/"; const char *path = (argc > 1) ? argv[1] : "/";
if (chdir(path) != 0) { if (chdir(path) != 0) {
@ -1327,6 +1544,7 @@ static int execute_builtin(int argc, char *argv[]) {
if (str_eq(argv[0], "man")) return builtin_man(argc, argv); if (str_eq(argv[0], "man")) return builtin_man(argc, argv);
if (str_eq(argv[0], "alias")) return builtin_alias(argc, argv); if (str_eq(argv[0], "alias")) return builtin_alias(argc, argv);
if (str_eq(argv[0], "unalias")) return builtin_unalias(argc, argv); if (str_eq(argv[0], "unalias")) return builtin_unalias(argc, argv);
if (str_eq(argv[0], "time")) return builtin_time(argc, argv);
if (str_eq(argv[0], ".")) { if (str_eq(argv[0], ".")) {
if (argc < 2) { if (argc < 2) {
set_color(g_color_error); set_color(g_color_error);
@ -1607,22 +1825,8 @@ static int execute_argv_inner(int argc, char *argv[], int depth, bool isolated,
char full_path[256]; char full_path[256];
int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path));
if (cmd_res == -2) {
set_color(g_color_error);
printf("bsh: is a directory: %s\n", argv[0]);
reset_color();
return 1;
}
if (cmd_res == -3) {
set_color(g_color_error);
printf("bsh: not executable: %s\n", argv[0]);
reset_color();
return 1;
}
if (cmd_res != 0) { if (cmd_res != 0) {
set_color(g_color_error); print_command_resolution_error("bsh", argv[0], cmd_res);
printf("bsh: command not found: %s\n", argv[0]);
reset_color();
return 1; return 1;
} }

View file

@ -1,3 +1,6 @@
// Copyright (c) 2026 janevers — Discord: viper0727 (https://github.com/janevers-sys)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "syscall.h" #include "syscall.h"

View file

@ -38,6 +38,7 @@ int main(int argc, char **argv) {
printf("clear - Clear the screen\n"); printf("clear - Clear the screen\n");
printf("exit - Exit the terminal\n"); printf("exit - Exit the terminal\n");
printf("net - Network tools\n"); printf("net - Network tools\n");
printf("time <cmd> - Measure command execution time\n");
printf("\nHint: Use Ctrl+C to force quit any running application.\n"); printf("\nHint: Use Ctrl+C to force quit any running application.\n");
return 0; return 0;
} }

View file

@ -9,6 +9,9 @@
#include "../mem/memory_manager.h" #include "../mem/memory_manager.h"
#include "sys/spinlock.h" #include "sys/spinlock.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static struct limine_framebuffer *g_fb = NULL; static struct limine_framebuffer *g_fb = NULL;
static uint32_t g_bg_color = 0xFF696969; static uint32_t g_bg_color = 0xFF696969;
@ -122,7 +125,9 @@ int graphics_get_fb_bpp(void) {
return g_fb ? g_fb->bpp : 0; return g_fb ? g_fb->bpp : 0;
} }
// Merge new dirty rect with existing one // faltten the structure, cache the edges, calculate the right and bottom edges
// calculate a new bounding box with some of my clever branchless math
// and perform exactly 1 Write to memory
static void merge_dirty_rect(int x, int y, int w, int h) { static void merge_dirty_rect(int x, int y, int w, int h) {
if (!g_dirty.active) { if (!g_dirty.active) {
g_dirty.x = x; g_dirty.x = x;
@ -130,47 +135,54 @@ static void merge_dirty_rect(int x, int y, int w, int h) {
g_dirty.w = w; g_dirty.w = w;
g_dirty.h = h; g_dirty.h = h;
g_dirty.active = true; g_dirty.active = true;
} else { return;
// Calculate union of two rectangles
int x1 = g_dirty.x;
int y1 = g_dirty.y;
int x2 = g_dirty.x + g_dirty.w;
int y2 = g_dirty.y + g_dirty.h;
int new_x1 = x;
int new_y1 = y;
int new_x2 = x + w;
int new_y2 = y + h;
g_dirty.x = new_x1 < x1 ? new_x1 : x1;
g_dirty.y = new_y1 < y1 ? new_y1 : y1;
g_dirty.w = (new_x2 > x2 ? new_x2 : x2) - g_dirty.x;
g_dirty.h = (new_y2 > y2 ? new_y2 : y2) - g_dirty.y;
} }
int gx = g_dirty.x;
int gy = g_dirty.y;
int gx2 = gx + g_dirty.w;
int gy2 = gy + g_dirty.h;
int nx2 = x + w;
int ny2 = y + h;
int new_x = MIN(gx, x);
int new_y = MIN(gy, y);
int new_x2 = MAX(gx2, nx2);
int new_y2 = MAX(gy2, ny2);
g_dirty.x = new_x;
g_dirty.y = new_y;
g_dirty.w = new_x2 - new_x;
g_dirty.h = new_y2 - new_y;
} }
void graphics_mark_dirty(int x, int y, int w, int h) { void graphics_mark_dirty(int x, int y, int w, int h) {
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x + w > get_screen_width()) {
w = get_screen_width() - x;
}
if (y + h > get_screen_height()) {
h = get_screen_height() - y;
}
if (w <= 0 || h <= 0) { if (w <= 0 || h <= 0) {
return; return;
} }
int x2 = x + w;
int y2 = y + h;
// Cache screen boundaries because then we can avoid multiple calls
int screen_w = get_screen_width();
int screen_h = get_screen_height();
int cx1 = MAX(0, x);
int cy1 = MAX(0, y);
int cx2 = MIN(screen_w, x2);
int cy2 = MIN(screen_h, y2);
int cw = cx2 - cx1;
int ch = cy2 - cy1;
if (cw <= 0 || ch <= 0) {
return;
}
uint64_t flags = spinlock_acquire_irqsave(&graphics_lock); uint64_t flags = spinlock_acquire_irqsave(&graphics_lock);
merge_dirty_rect(x, y, w, h); merge_dirty_rect(cx1, cy1, cw, ch);
spinlock_release_irqrestore(&graphics_lock, flags); spinlock_release_irqrestore(&graphics_lock, flags);
} }

View file

@ -244,10 +244,10 @@ void wallpaper_init(void) {
wallpaper_load_setting(); wallpaper_load_setting();
if (pending_wallpaper_path == NULL) { if (pending_wallpaper_path == NULL) {
if (fat32_exists("/Library/images/Wallpapers/boredos.png")) { if (fat32_exists("/Library/images/Wallpapers/Drift.png")) {
wallpaper_request_set_from_file("/Library/images/Wallpapers/boredos.png"); wallpaper_request_set_from_file("/Library/images/Wallpapers/Drift.png");
} else if (fat32_exists("/Library/images/Wallpapers/mountain.jpg")) { } else if (fat32_exists("/Library/images/Wallpapers/orbital.jpg")) {
wallpaper_request_set_from_file("/Library/images/Wallpapers/mountain.jpg"); wallpaper_request_set_from_file("/Library/images/Wallpapers/orbital.jpg");
} }
} }
} }

View file

@ -220,6 +220,8 @@ static int pending_dock_click_index = -1;
static int dock_drag_source_index = -1; static int dock_drag_source_index = -1;
static bool dock_drag_active = false; static bool dock_drag_active = false;
static int pending_desktop_icon_click = -1; static int pending_desktop_icon_click = -1;
static int drag_repaint_accum_x = 0;
static int drag_repaint_accum_y = 0;
// Desktop Context Menu // Desktop Context Menu
static bool desktop_menu_visible = false; static bool desktop_menu_visible = false;
@ -3362,10 +3364,22 @@ static void wm_handle_mouse_internal(int dx, int dy, uint8_t buttons, int dz) {
} else if (right && !prev_right) { } else if (right && !prev_right) {
wm_handle_right_click(mx, my); wm_handle_right_click(mx, my);
} else if (left && is_dragging && drag_window) { } else if (left && is_dragging && drag_window) {
int old_x = drag_window->x;
int old_y = drag_window->y;
drag_window->x = mx - drag_offset_x; drag_window->x = mx - drag_offset_x;
drag_window->y = my - drag_offset_y; drag_window->y = my - drag_offset_y;
// Mark for full redraw since window moved
force_redraw = true; drag_repaint_accum_x += (drag_window->x - old_x < 0 ? -(drag_window->x - old_x) : (drag_window->x - old_x));
drag_repaint_accum_y += (drag_window->y - old_y < 0 ? -(drag_window->y - old_y) : (drag_window->y - old_y));
// Only repaint if we've moved at least 2px because we dont wanna redraw for subpixels
if (drag_repaint_accum_x >= 2 || drag_repaint_accum_y >= 2) {
wm_mark_dirty(old_x - 2, old_y - 2, drag_window->w + 4, drag_window->h + 4);
wm_mark_dirty(drag_window->x - 2, drag_window->y - 2, drag_window->w + 4, drag_window->h + 4);
drag_repaint_accum_x = 0;
drag_repaint_accum_y = 0;
}
} else if (left && is_resizing && drag_window) { } else if (left && is_resizing && drag_window) {
int new_w = mx - drag_window->x + (drag_start_w - drag_offset_x); int new_w = mx - drag_window->x + (drag_start_w - drag_offset_x);
int new_h = my - drag_window->y + (drag_start_h - drag_offset_y); int new_h = my - drag_window->y + (drag_start_h - drag_offset_y);