mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-05-30 18:36:58 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e123b6429 | ||
|
|
4177484366 | ||
|
|
8dc5ee5867 | ||
|
|
884c2f8980 | ||
|
|
36d61e3b7b | ||
|
|
013f0b513f | ||
|
|
28108adde3 | ||
|
|
62ac2ab849 | ||
|
|
7f510c6aa5 | ||
|
|
7116de4152 | ||
|
|
049d67e821 | ||
|
|
0f3971bb1c | ||
|
|
66f55242a7 | ||
|
|
8a8fb7de27 | ||
|
|
914c60e1f1 | ||
|
|
5141eaea60 | ||
|
|
6e90c3e197 | ||
|
|
bdd43f43cd | ||
|
|
a8866da3cb | ||
|
|
14decdd705 | ||
|
|
ed73b88ec1 | ||
|
|
f9bc6c7c38 | ||
|
|
bb187faf79 | ||
|
|
fd7fa4f16e | ||
|
|
5bd9e537c5 | ||
|
|
e4603792b6 | ||
|
|
a27b2c6423 | ||
|
|
bb176f2193 | ||
|
|
8dd756f25b | ||
|
|
d13fca2d4a | ||
|
|
a1b6d58b77 | ||
|
|
cbc196a4b1 | ||
|
|
b4c14af48d | ||
|
|
700839e6be | ||
|
|
921e8a5658 | ||
|
|
437d57312f | ||
|
|
afc4e16fcf | ||
|
|
38ed0b5ffa | ||
|
|
5933483009 | ||
|
|
6b6a22d518 | ||
|
|
85427041de | ||
|
|
8b77e8c48e | ||
|
|
1ce08c70b0 | ||
|
|
fca67f68a9 | ||
|
|
c330382436 | ||
|
|
f0c2963793 | ||
|
|
3b24bc882c | ||
|
|
2b44e59e9f | ||
|
|
7a2769e8e3 | ||
|
|
1a6e30b52e | ||
|
|
69847adee6 | ||
|
|
f402e5e4f0 | ||
|
|
684ed774ee | ||
|
|
9ed8eac3e5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,3 +30,4 @@ limine
|
||||
.DS_Store
|
||||
/build/
|
||||
*.o
|
||||
disk.img
|
||||
|
||||
23
Makefile
23
Makefile
@@ -139,6 +139,7 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/bsh
|
||||
mkdir -p $(BUILD_DIR)/initrd/docs
|
||||
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
@@ -156,6 +157,9 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \
|
||||
done
|
||||
@if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi
|
||||
@if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
|
||||
@if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
|
||||
@for f in $$(find docs -name '*.md' 2>/dev/null); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
@@ -166,6 +170,7 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
done
|
||||
@if [ -f README.md ]; then cp README.md $(BUILD_DIR)/initrd/; fi
|
||||
@if [ -f LICENSE ]; then cp LICENSE $(BUILD_DIR)/initrd/; fi
|
||||
@if [ -f limine.conf ]; then cp limine.conf $(BUILD_DIR)/initrd/; fi
|
||||
|
||||
cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar *
|
||||
|
||||
@@ -201,12 +206,26 @@ clean:
|
||||
rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE)
|
||||
$(MAKE) -C $(SRC_DIR)/userland clean
|
||||
|
||||
run: $(ISO_IMAGE)
|
||||
run-windows: $(ISO_IMAGE)
|
||||
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
|
||||
-smp 4 \
|
||||
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
|
||||
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
|
||||
-drive file=disk.img,format=raw,file.locking=off
|
||||
run-mac: $(ISO_IMAGE)
|
||||
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
|
||||
-smp 4 \
|
||||
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
|
||||
-netdev user,id=net0,hostfwd=udp::12346-:12345 -device virtio-net-pci,netdev=net0 \
|
||||
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
|
||||
-display cocoa,show-cursor=off \
|
||||
-drive file=disk.img,format=raw,file.locking=off \
|
||||
-cpu max
|
||||
run-linux: $(ISO_IMAGE)
|
||||
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
|
||||
-smp 4 \
|
||||
-audiodev pa,id=audio0 -machine pcspk-audiodev=audio0 \
|
||||
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
|
||||
-display gtk,show-cursor=off \
|
||||
-drive file=disk.img,format=raw,file.locking=off \
|
||||
-cpu max
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -19,9 +19,9 @@ BoredOS is a x86_64 operating system featuring a custom Desktop Environment (DE)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
## Features
|
||||
|
||||
### ⚙️ System Architecture
|
||||
### System Architecture
|
||||
* **64-bit Long Mode:** Fully utilizing the x86_64 architecture.
|
||||
* **Symmetric Multi-Processing (SMP):** Full support for multi-core CPUs via Limine SMP.
|
||||
* **LAPIC & IPI Scheduling:** Advanced interrupt handling and inter-processor communication for task distribution.
|
||||
@@ -29,14 +29,14 @@ BoredOS is a x86_64 operating system featuring a custom Desktop Environment (DE)
|
||||
* **Multiboot2 Compliant:** Bootable on real hardware and modern emulators.
|
||||
* **Kernel Core:** Interrupt Descriptor Table (IDT) management and a robust syscall interface.
|
||||
* **Filesystem:** Full **FAT32** support for persistent and in-memory storage.
|
||||
* **Networking:** Includes the lwIP networking stack.
|
||||
* **Networking:** Includes the lwIP networking stack and a basic web browser.
|
||||
|
||||
### 📺 Graphical User Interface
|
||||
### Graphical User Interface
|
||||
* **BoredWM:** A custom Window Manager with drag-and-drop, mouse-centered interaction.
|
||||
* **Customization:** Adjustable UI to suit your aesthetic.
|
||||
* **Media Support:** Built-in image decoding.
|
||||
|
||||
### 🛠️ Included Applications
|
||||
### Included Applications
|
||||
* **Productivity:** GUI Text Editor calculator, Markdown Viewer, a simple browser and BoredWord.
|
||||
* **Creativity:** A Paint application.
|
||||
* **Utilities:** Terminal, Task Manager, File Explorer, Clock and a (limited) C Compiler.
|
||||
@@ -55,11 +55,11 @@ Explore the internal workings of BoredOS via our comprehensive guides in the [`d
|
||||
|
||||
---
|
||||
|
||||
## ☕ Support the Journey
|
||||
## Support the Journey
|
||||
|
||||
If you find this project interesting or helpful, consider fueling the development with a coffee!
|
||||
|
||||
<a href="https://buymeacoffee.com/boreddevnl" target="_blank">
|
||||
<a href="https://buymeacoffee.com/boreddevhq" target="_blank">
|
||||
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" style="border-radius: 8px;" />
|
||||
</a>
|
||||
|
||||
@@ -68,16 +68,16 @@ If you find this project interesting or helpful, consider fueling the developmen
|
||||
|
||||
## ⚠️ Project Disclaimer & Heritage
|
||||
|
||||
**BoredOS** is the successor to **BrewKernel**, a text-only project initiated in 2023.
|
||||
**BoredOS** is the successor to **BrewKernel**, a project initiated in 2023.
|
||||
|
||||
While BrewKernel served as the foundational learning ground for this OS, it has been officially **deprecated and archived**. It no longer receives updates, bug fixes, or pull request reviews. BoredOS represents a complete architectural reboot, applying years of lessons learned to create a cleaner, more modular, and more capable 64-bit system.
|
||||
While BrewKernel served as the foundational learning ground for this OS, it has been officially **deprecated and archived**. It no longer receives updates, bug fixes, or pull request reviews. BoredOS represents a complete architectural reboot, applying years of lessons learned to create a cleaner, more modular, and more capable system.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please ensure all issues, discussions, and contributions are directed to this repository. Legacy BrewKernel code is preserved for historical purposes only and is not compatible with BoredOS.
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ License
|
||||
## License
|
||||
|
||||
**Copyright (C) 2024-2026 boreddevnl**
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
---
|
||||
|
||||
Welcome to the internal documentation for BoredOS! This directory contains detailed guides on how the OS functions, how to build it, and how to develop applications for it.
|
||||
Welcome to the documentation for BoredOS! This directory contains detailed guides on how the OS functions, how to build it, and how to develop applications for it.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
@@ -27,7 +27,17 @@ Instructions for compiling the OS from source.
|
||||
The SDK and toolchain guides for creating your own `.elf` userland binaries.
|
||||
- [`SDK Reference`](appdev/sdk_reference.md): Explanation of the custom `libc` wrappers (`stdlib.h`, `string.h`) and system calls.
|
||||
- [`UI API`](appdev/ui_api.md): Drawing on the screen, creating windows, and polling the event loop using `libui.h`.
|
||||
- [`Widget API`](appdev/widget_api.md): High-level UI components like buttons, textboxes, and scrollbars using `libwidget.h`.
|
||||
- [`Custom Apps`](appdev/custom_apps.md): A step-by-step tutorial on writing a new graphical C application, editing the Makefile, and bundling it into the ISO.
|
||||
- [`Example Apps`](appdev/examples/README.md): A collection of sample C applications ranging from basic terminal output to advanced TCP networking.
|
||||
- [`Grapher`](appdev/grapher.md): Full reference for the built-in mathematical graphing application — equation syntax, keyboard controls, architecture, and configuration.
|
||||
|
||||
### 4. [Usage](usage/)
|
||||
General guides on how to interact with the OS.
|
||||
- [`Booting`](usage/booting.md): How to use the Limine bootloader and toggle kernel boot flags like `-v`.
|
||||
- [`Desktop`](usage/desktop.md): Window management, shortcuts, and desktop interaction.
|
||||
- [`Lumos`](usage/lumos.md): Using the system-wide search (`Shift + Ctrl + Space`).
|
||||
- [`Terminal`](usage/terminal.md): Command line interface, redirection, and common commands.
|
||||
- [`Launching Apps`](usage/launching_apps.md): Ways to launch files and applications, plus a software overview.
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@ This guide explains how to write a new "Hello World" application locally, compil
|
||||
> [!TIP]
|
||||
> **Looking for working code?** Check out the [Examples Directory](examples/README.md) for full source code demonstrating basic CLI, Windows, Animations, and TCP Networking.
|
||||
|
||||
## 📝 Step 1: Write the C Source
|
||||
## Step 1: Write the C Source
|
||||
|
||||
Applications reside entirely in the `src/userland/` directory. Create a new file, for example, `src/userland/gui/hello.c`.
|
||||
|
||||
@@ -52,7 +52,7 @@ int main(void) {
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ Step 2: Edit the Makefile
|
||||
## Step 2: Edit the Makefile
|
||||
|
||||
Now you need to tell the build system to compile `hello.c`. Fortunately, the `src/userland/Makefile` is designed to detect new C files largely automatically!
|
||||
|
||||
@@ -64,7 +64,7 @@ Now you need to tell the build system to compile `hello.c`. Fortunately, the `sr
|
||||
Since you placed the file in `gui/hello.c`, the wildcard logic will pick it up automatically.
|
||||
3. The Makefile will generate `bin/hello.elf` during the build phase.
|
||||
|
||||
## 📦 Step 3: Bundle it into the OS
|
||||
## Step 3: Bundle it into the OS
|
||||
|
||||
The main overarching `Makefile` (in the project root) takes binaries from `src/userland/bin/*.elf` and places them into the `iso_root/bin/` directory, while also adding them to `limine.conf` as loadable boot modules.
|
||||
|
||||
@@ -77,7 +77,7 @@ The main overarching `Makefile` (in the project root) takes binaries from `src/u
|
||||
make clean && make run
|
||||
```
|
||||
|
||||
## 🚀 Step 4: Run it inside BoredOS
|
||||
## Step 4: Run it inside BoredOS
|
||||
|
||||
1. When BoredOS boots, launch the **Terminal** application.
|
||||
2. The OS automatically maps built applications to standard shell commands. Simply type your application's filename (without the `.elf` extension).
|
||||
|
||||
345
docs/appdev/grapher.md
Normal file
345
docs/appdev/grapher.md
Normal file
@@ -0,0 +1,345 @@
|
||||
<div align="center">
|
||||
<h1>Grapher</h1>
|
||||
<p><em>An interactive mathematical expression plotter for BoredOS, supporting both 2D and 3D visualizations.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Grapher is a built-in GUI application that lets you type any mathematical equation and see it plotted in real time. It supports 2D explicit and implicit curves as well as full 3D surface visualization — including both explicit surfaces (`z = f(x, y)`) and implicit surfaces (`f(x, y, z) = c`).
|
||||
|
||||
> [!NOTE]
|
||||
> Grapher is located at `src/userland/gui/grapher.c`. It runs as a standard BoredOS GUI process and can be launched from the terminal or from the dock.
|
||||
|
||||
---
|
||||
|
||||
## Features at a Glance
|
||||
|
||||
| Feature | Details |
|
||||
|---|---|
|
||||
| **2D Explicit** | Plot `y = f(x)` curves |
|
||||
| **2D Implicit** | Plot any `f(x, y) = g(x, y)` contour via marching squares |
|
||||
| **3D Explicit** | Plot `z = f(x, y)` surfaces |
|
||||
| **3D Implicit** | Plot any `f(x, y, z) = c` surface |
|
||||
| **Rendering modes** | Wireframe and filled polygon modes |
|
||||
| **Height coloring** | Surfaces are colored by a blue→green→yellow→red gradient based on Z height |
|
||||
| **Phong-style shading** | Filled mode computes per-face normals and applies diffuse + ambient lighting |
|
||||
| **Parallel rendering** | Evaluation and projection are distributed across 4 worker threads via `sys_parallel_run` |
|
||||
| **Preset equations** | 7 built-in presets accessible from the toolbar |
|
||||
| **Auto-fit** | 2D view auto-fits the Y axis to the plotted curve on first plot |
|
||||
| **Atomic Color-Depth Buffer** | All 3D drawing uses a 64-bit atomic buffer to prevent depth/color race conditions |
|
||||
|
||||
---
|
||||
|
||||
## Launching Grapher
|
||||
|
||||
From the BoredOS terminal:
|
||||
```sh
|
||||
grapher
|
||||
```
|
||||
|
||||
Or click the **Grapher icon** in the system dock.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### Toolbar Controls
|
||||
|
||||
| Control | Function |
|
||||
|---|---|
|
||||
| **Equation box** | Type your mathematical expression, then press **Enter** or **Plot** |
|
||||
| **Plot button** | Parse and render the current equation |
|
||||
| **Wire / Filled button** | Toggle wireframe vs. shaded polygon mode (3D only) |
|
||||
| **Presets button** | Open a dropdown of example equations |
|
||||
|
||||
### Status Bar Controls (3D mode)
|
||||
|
||||
| Control | Function |
|
||||
|---|---|
|
||||
| **`+` button** | Increase the 3D world range (zoom out in world space) |
|
||||
| **`-` button** | Decrease the 3D world range (zoom in in world space) |
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|---|---|
|
||||
| **Enter** (in equation box) | Plot the equation |
|
||||
| **Ctrl + R** | Reset the view to defaults |
|
||||
| **F** | Toggle filled / wireframe rendering (3D mode) |
|
||||
| **Scroll wheel** | Zoom in/out (2D mode adjusts viewport; 3D mode adjusts camera zoom) |
|
||||
| **Right-click drag** | Rotate the 3D surface |
|
||||
|
||||
---
|
||||
|
||||
## Writing Equations
|
||||
|
||||
Grapher parses equations entered as plain text. It supports a subset of mathematical notation with automatic implicit multiplication.
|
||||
|
||||
### Supported Functions
|
||||
|
||||
| Syntax | Meaning |
|
||||
|---|---|
|
||||
| `sin(x)` | Sine |
|
||||
| `cos(x)` | Cosine |
|
||||
| `tan(x)` | Tangent |
|
||||
| `sqrt(x)` | Square root |
|
||||
| `abs(x)` | Absolute value |
|
||||
| `log(x)` | Natural logarithm (base *e*) |
|
||||
|
||||
### Supported Operators
|
||||
|
||||
| Operator | Meaning |
|
||||
|---|---|
|
||||
| `+` `-` `*` `/` | Arithmetic |
|
||||
| `^` | Exponentiation (right-associative) |
|
||||
| `(` `)` | Grouping |
|
||||
|
||||
### Special Values
|
||||
|
||||
| Token | Value |
|
||||
|---|---|
|
||||
| `pi` or `PI` | π ≈ 3.14159… |
|
||||
|
||||
### Implicit Multiplication
|
||||
|
||||
Adjacent tokens that would normally require a `*` are multiplied automatically:
|
||||
|
||||
```
|
||||
2x → 2 * x
|
||||
3sin(x) → 3 * sin(x)
|
||||
(x+1)(x) → (x+1) * x
|
||||
```
|
||||
|
||||
### How Equations Are Classified
|
||||
|
||||
Grapher looks at which variables appear in your equation to automatically choose the rendering mode:
|
||||
|
||||
| Equation form | Auto-detected as |
|
||||
|---|---|
|
||||
| `y = f(x)` or just `f(x)` | 2D explicit |
|
||||
| `f(x, y) = g(x, y)` | 2D implicit |
|
||||
| `z = f(x, y)` | 3D explicit |
|
||||
| `f(x, y, z) = c` | 3D implicit |
|
||||
|
||||
If you omit the `=` sign, Grapher treats the input as `y = <expression>` when no `y` or `z` is present, or as `<expression> = 0` otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Example Equations
|
||||
|
||||
### 2D Examples
|
||||
|
||||
```
|
||||
y = sin(x)
|
||||
y = x^2
|
||||
y = cos(x)*x
|
||||
y = abs(x) - 2
|
||||
x^2 + y^2 = 25 ← circle (implicit)
|
||||
y = log(x)
|
||||
```
|
||||
|
||||
### 3D Explicit Examples
|
||||
|
||||
```
|
||||
z = sin(x)*cos(y)
|
||||
z = x^2 - y^2 ← saddle surface
|
||||
z = sqrt(25 - x^2 - y^2)
|
||||
```
|
||||
|
||||
### 3D Implicit Examples
|
||||
|
||||
```
|
||||
x^2 + y^2 + z^2 = 25 ← sphere
|
||||
x^2 + y^2 = 16 ← cylinder
|
||||
x^2 + y^2 - z^2 = 1 ← hyperboloid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Navigation Controls
|
||||
|
||||
### 2D Mode
|
||||
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| **Scroll up** | Zoom in |
|
||||
| **Scroll down** | Zoom out |
|
||||
| **Ctrl+R** | Reset to default view (`x: [-10, 10]`) |
|
||||
|
||||
### 3D Mode
|
||||
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| **Right-click drag** | Rotate the surface (orbit camera) |
|
||||
| **Scroll up** | Zoom camera in |
|
||||
| **Scroll down** | Zoom camera out |
|
||||
| **`+` / `-` buttons** | Increase / decrease world range |
|
||||
| **Ctrl+R** | Reset rotation and zoom |
|
||||
|
||||
> [!TIP]
|
||||
> In 3D mode, the surface auto-rotates slowly by default. This can be disabled by setting `#define ROTATE 0` in the source file.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Grapher is implemented as a single self-contained C file. Below is a high-level breakdown of its major components:
|
||||
|
||||
### Math Library
|
||||
|
||||
Grapher uses the BoredOS freestanding **`libc/math.h`** library, which provides all the math functions it needs without depending on a host standard library:
|
||||
|
||||
| Function | Description |
|
||||
|---|---|
|
||||
| `sin`, `cos`, `tan` | Trigonometry via Taylor series (8 terms, range-reduced to `[-π, π]`) |
|
||||
| `sqrt` | Newton-Raphson iteration (25 steps) |
|
||||
| `log` | Natural logarithm via Padé-style series |
|
||||
| `log2`, `log10` | Derived from `log` |
|
||||
| `exp` | Range-reduced Taylor series for `e^x` |
|
||||
| `pow` | Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))` |
|
||||
| `fabs`, `fmod` | Absolute value and floating-point remainder |
|
||||
| `floor`, `ceil` | Rounding |
|
||||
| `sinh`, `cosh`, `tanh` | Hyperbolic functions |
|
||||
| `hypot`, `fmin`, `fmax`, `fclamp` | Utility helpers |
|
||||
|
||||
The constants `M_PI`, `M_E`, `M_LN2`, `M_SQRT2` are also defined in the header.
|
||||
|
||||
This library is automatically linked into every userland ELF — any app can `#include "math.h"` to use it.
|
||||
|
||||
### Expression Parser
|
||||
|
||||
Equations are parsed in three stages:
|
||||
|
||||
1. **Tokenizer** (`tokenize`) — converts the input string into a flat token array. Handles implicit multiplication by inserting `*` tokens where needed.
|
||||
2. **Recursive Descent Parser** (`parse_expr`, `parse_term`, `parse_power`, `parse_unary`, `parse_atom`) — produces an Abstract Syntax Tree (AST) with up to `MAX_NODES = 128` nodes.
|
||||
3. **Bytecode Compiler** (`compile_ast`) — walks the AST in post-order and emits a flat instruction sequence for a simple stack machine. This avoids recursive evaluation during rendering hot paths.
|
||||
|
||||
The resulting bytecode is then executed by `run_bc` for every sample point.
|
||||
|
||||
### Rendering Pipeline
|
||||
|
||||
#### 2D Rendering
|
||||
|
||||
- **Explicit** — evaluates `y = f(x)` at every pixel column and connects adjacent samples with Bresenham lines.
|
||||
- **Implicit** — applies **marching squares** on a 200×130 grid to find sign changes in `f(x,y) - g(x,y)` and plots intersection pixels.
|
||||
|
||||
#### 3D Rendering
|
||||
|
||||
The 3D pipeline uses a multi-pass system parallelized across worker threads:
|
||||
|
||||
| Pass | Function | Description |
|
||||
|---|---|---|
|
||||
| 1 | **Evaluation** | Samples the surface at grid points. For implicit surfaces, this uses **tri-axis marching**. |
|
||||
| 2 | **Projection** | Projects 3D world coordinates to 2D screen coordinates with perspective. |
|
||||
| 3 | **Drawing** | Rasterizes wireframe lines or filled triangles with Z-buffering. |
|
||||
|
||||
##### Tri-Axis Marching (Implicit Surfaces)
|
||||
|
||||
Unlike explicit surfaces that only need one evaluation per grid point, implicit surfaces require finding roots of $f(x, y, z) = 0$. To ensure complete surface connectivity and eliminate "cracks," Grapher marches along all three primary axes:
|
||||
|
||||
1. **X-Axis Pass**: For every $(y, z)$ pair, march along $x$.
|
||||
2. **Y-Axis Pass**: For every $(x, z)$ pair, march along $y$.
|
||||
3. **Z-Axis Pass**: For every $(x, y)$ pair, march along $z$.
|
||||
|
||||
Each pass uses a multi-stage root finder (170 linear steps followed by 15 bisection iterations). By sampling along all three axes, the engine "catches" surfaces that are nearly parallel to any specific marching direction, ensuring that vertical walls and steep gradients are rendered solidly from any viewing angle.
|
||||
|
||||
##### Atomic Color-Depth Buffer
|
||||
|
||||
To prevent "z-fighting" and race conditions between parallel threads, Grapher uses a 64-bit atomic buffer (`graph_czb`). Each 64-bit word stores:
|
||||
- **Upper 32 bits**: Z-depth (integer).
|
||||
- **Lower 32 bits**: Pixel color (0xAARRGGBB).
|
||||
|
||||
A single `__atomic_compare_exchange_n` operation ensures that a pixel's color and depth are updated together only if the new depth is closer to the camera than the existing one.
|
||||
|
||||
Surface normals are estimated using central finite differences of the implicit function.
|
||||
|
||||
#### Filled Mod
|
||||
|
||||
When filled mode is active, each quad cell is split into two triangles. The average surface normal across the four corner vertices is computed and fed into `apply_shading`, which calculates:
|
||||
|
||||
```
|
||||
intensity = ambient(0.3) + diffuse(0.7) * dot(normal, light_direction)
|
||||
```
|
||||
|
||||
The light direction is fixed at `(0.577, 0.707, 0.408)` (normalized diagonal).
|
||||
|
||||
#### Z-Buffer
|
||||
|
||||
The depth buffer (`graph_zb`) stores integer depth values. `gfb_pixel_z` uses a **compare-and-swap (CAS) loop** via `__atomic_compare_exchange_n` so multiple parallel draw threads cannot produce race conditions.
|
||||
|
||||
### Coordinate Systems
|
||||
|
||||
#### 2D
|
||||
|
||||
World coordinates map linearly to screen pixels:
|
||||
|
||||
```c
|
||||
screen_x = (wx - view_x_min) / (view_x_max - view_x_min) * graph_w
|
||||
screen_y = (view_y_max - wy) / (view_y_max - view_y_min) * graph_h
|
||||
```
|
||||
|
||||
#### 3D
|
||||
|
||||
Points are first rotated by two Euler angles (`rot_y`, `rot_x`) then projected with a simple perspective divide:
|
||||
|
||||
```
|
||||
persp = d / (pz + d) // d = range_3d * 5
|
||||
sx = px * scale * persp + screen_cx
|
||||
sy = -py * scale * persp + screen_cy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Constants
|
||||
|
||||
These can be changed at the top of `grapher.c` to tune behaviour:
|
||||
|
||||
| Constant | Default | Effect |
|
||||
|---|---|---|
|
||||
| `ROTATE` | `1` | Set to `0` to disable auto-rotation in 3D mode |
|
||||
| `GRID_3D` | `41` | Grid resolution for 3D sampling. Higher = more detail, much slower |
|
||||
|
||||
> [!WARNING]
|
||||
> Setting `GRID_3D` too high (e.g. 9000) will exhaust available memory. The `surf` grid and `surf_x`/`surf_y_3d` arrays are statically allocated at compile time: memory usage grows as **O(GRID_3D²)**. Values above ~512 are not recommended.
|
||||
|
||||
> [!TIP]
|
||||
> `GRID_3D = 256` gives a good balance of detail and performance on typical BoredOS hardware emulation.
|
||||
|
||||
---
|
||||
|
||||
## Color Palette
|
||||
|
||||
|
||||
3D surfaces are colored by height using a 4-stop rainbow ramp:
|
||||
|
||||
```
|
||||
Low → Blue → Cyan → Green → Yellow → Red → High
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preset Equations
|
||||
|
||||
The built-in presets are shown in the dropdown when you click **Presets**:
|
||||
|
||||
| Label | Type |
|
||||
|---|---|
|
||||
| `y = sin(x)` | 2D explicit |
|
||||
| `y = x^2` | 2D explicit |
|
||||
| `y = cos(x)*x` | 2D explicit |
|
||||
| `z = sin(x)*cos(y)` | 3D explicit |
|
||||
| `z = x^2 - y^2` | 3D explicit |
|
||||
| `x^2+y^2+z^2=25` | 3D implicit (sphere) |
|
||||
| `x^2+y^2=16` | 3D implicit (cylinder) |
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- **No parameter slider** — equations are static; there is no way to animate a parameter.
|
||||
- **No multiple equations** — only one equation can be graphed at a time.
|
||||
- **Implicit surface precision** — extremely thin or high-frequency implicit surfaces may still have small artifacts if the grid resolution (`GRID_3D`) is too low.
|
||||
- **3D implicit performance** — tri-axis marching evaluates the function significantly more times than explicit rendering; high resolutions will impact frame rate.
|
||||
- **Integer axis labels only for large values** — very large axis values are capped at `>2G` or `<-2G` due to `itoa` limitations.
|
||||
@@ -7,9 +7,13 @@
|
||||
|
||||
BoredOS provides a custom `libc` implementation necessary for writing userland applications (`.elf` binaries). By avoiding a full-blown standard library like `glibc`, the OS ensures a minimal executable footprint tailored strictly to the existing kernel features.
|
||||
|
||||
All headers are located in `src/userland/libc/`.
|
||||
All headers are located in `src/userland/libc/` (standard functions) and `src/wm/` (UI and widgets).
|
||||
- `stdlib.h`: Memory, strings, and basic I/O.
|
||||
- `math.h`: Freestanding floating-point math library.
|
||||
- `libui.h`: Core window and drawing API.
|
||||
- `libwidget.h`: High-level UI components.
|
||||
|
||||
## 📚 Standard Library (`stdlib.h` & `string.h`)
|
||||
## Standard Library (`stdlib.h` & `string.h`)
|
||||
|
||||
The standard library wrappers provide memory management, string manipulation, and basic IO formatting without needing direct syscalls.
|
||||
|
||||
@@ -45,7 +49,60 @@ The standard library wrappers provide memory management, string manipulation, an
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ System Calls (`syscall.h`)
|
||||
## Math Library (`math.h`)
|
||||
|
||||
BoredOS ships a freestanding floating-point math library in `libc/math.h`. It uses pure arithmetic — Taylor series, Newton-Raphson, and range-reduction — with no dependency on a host `libm` or hardware math intrinsics. It is automatically linked into every userland ELF.
|
||||
|
||||
```c
|
||||
#include "math.h"
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---|---|---|
|
||||
| `M_PI` | 3.14159… | π |
|
||||
| `M_E` | 2.71828… | Euler's number |
|
||||
| `M_LN2` | 0.69315… | Natural log of 2 |
|
||||
| `M_SQRT2` | 1.41421… | √2 |
|
||||
| `HUGE_VAL` | ~+∞ | Overflow sentinel |
|
||||
|
||||
### Functions
|
||||
|
||||
#### Absolute value & remainder
|
||||
* `double fabs(double x);` — Absolute value.
|
||||
* `double fmod(double x, double y);` — Floating-point remainder. Returns `0` when `y == 0`.
|
||||
|
||||
#### Rounding
|
||||
* `double floor(double x);` — Largest integer ≤ x.
|
||||
* `double ceil(double x);` — Smallest integer ≥ x.
|
||||
|
||||
#### Trigonometry *(arguments in radians)*
|
||||
* `double sin(double x);` — Sine. Range-reduced to `[-π, π]` then computed via 8-term Taylor series.
|
||||
* `double cos(double x);` — Cosine. Computed via `sin(x + π/2)`.
|
||||
* `double tan(double x);` — Tangent. Returns sentinel `1e15` near poles.
|
||||
|
||||
#### Exponential & logarithm
|
||||
* `double sqrt(double x);` — Square root via Newton-Raphson (25 iterations). Returns `0` for `x ≤ 0`.
|
||||
* `double log(double x);` — Natural logarithm (ln). Returns `-1e30` for `x ≤ 0`.
|
||||
* `double log2(double x);` — Base-2 logarithm.
|
||||
* `double log10(double x);` — Base-10 logarithm.
|
||||
* `double exp(double x);` — e^x. Saturates to `1e300` for `x > 700`, `0` for `x < -700`.
|
||||
* `double pow(double base, double exponent);` — General power. Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))`.
|
||||
|
||||
#### Hyperbolic
|
||||
* `double sinh(double x);` — Hyperbolic sine.
|
||||
* `double cosh(double x);` — Hyperbolic cosine.
|
||||
* `double tanh(double x);` — Hyperbolic tangent.
|
||||
|
||||
#### Utility
|
||||
* `double hypot(double x, double y);` — `sqrt(x² + y²)` without intermediate overflow.
|
||||
* `double fmin(double a, double b);` — Minimum of two values.
|
||||
* `double fmax(double a, double b);` — Maximum of two values.
|
||||
* `double fclamp(double x, double lo, double hi);` — Clamps `x` into `[lo, hi]`.
|
||||
|
||||
> [!NOTE]
|
||||
> The implementation file is named `libc/libmath.c` (not `libc/math.c`) to avoid a name collision with the `math` CLI calculator app in userland. The public header is still included as `#include "math.h"`.
|
||||
|
||||
For advanced operations, `syscall.h` provides direct wrappers into the kernel.
|
||||
|
||||
@@ -108,7 +165,7 @@ BoredOS includes lwIP for hardware TCP/UDP networking.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Core Data Structures
|
||||
## Core Data Structures
|
||||
|
||||
### `os_info_t`
|
||||
Contains detailed build and version information about the OS.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
The UI library (`libui.h`) is the sole mechanism for Graphical Userland Applications to draw to the screen and receive input events in BoredOS. It wraps `SYS_GUI` kernel calls.
|
||||
|
||||
## 🪟 Window Management
|
||||
## Window Management
|
||||
|
||||
A "Window" is a reserved drawing canvas managed by the compositor.
|
||||
|
||||
@@ -21,7 +21,7 @@ A "Window" is a reserved drawing canvas managed by the compositor.
|
||||
* `void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h);`
|
||||
Query the global screen resolution of the display.
|
||||
|
||||
## 🎨 Drawing Primitives
|
||||
## Drawing Primitives
|
||||
|
||||
All drawing functions write to an off-screen buffer associated with the window. **You must call `ui_mark_dirty()` to instruct the compositor to push your changes to the physical screen.**
|
||||
|
||||
@@ -38,7 +38,7 @@ All drawing functions write to an off-screen buffer associated with the window.
|
||||
> Colors are defined as 32-bit unsigned integers in **ARGB** format: `0xAARRGGBB`.
|
||||
> E.g., `0xFF000000` is opaque black, `0xFFFF0000` is opaque red.
|
||||
|
||||
## 🔤 Text Rendering
|
||||
## Text Rendering
|
||||
|
||||
BoredOS provides multiple text rendering methodologies, including a default system font and scaled/bitmap alternatives.
|
||||
|
||||
@@ -60,7 +60,7 @@ Used for calculating layout bounds before drawing:
|
||||
* `uint32_t ui_get_string_width_scaled(const char *str, float scale);`
|
||||
* `uint32_t ui_get_font_height_scaled(float scale);`
|
||||
|
||||
## 🔄 Event Handling
|
||||
## Event Handling
|
||||
|
||||
Applications must continuously poll for events inside an infinite `$while(1)` loop.
|
||||
|
||||
@@ -103,3 +103,9 @@ typedef struct {
|
||||
*(Note: Coordinate arguments (`arg1`, `arg2`) for mouse events are typically relative to the top-left corner of the window's client area).*
|
||||
|
||||
---
|
||||
|
||||
> [!TIP]
|
||||
> **Looking for Buttons, TextBoxes, or Scrollbars?**
|
||||
> While `libui.h` provides the foundation for drawing, most applications should use the higher-level [**Widget API**](widget_api.md) (`libwidget.h`) for standard interactive components.
|
||||
|
||||
---
|
||||
|
||||
108
docs/appdev/widget_api.md
Normal file
108
docs/appdev/widget_api.md
Normal file
@@ -0,0 +1,108 @@
|
||||
<div align="center">
|
||||
<h1>Widget API (<code>libwidget.h</code>)</h1>
|
||||
<p><em>High-level UI components for BoredOS applications.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
The Widget library (`libwidget.h`) provides a set of reusable UI components built on top of `libui.h`. It uses an abstract `widget_context_t` to decouple component logic from specific drawing implementations, making it easier to build complex graphical interfaces.
|
||||
|
||||
## Widget Context
|
||||
|
||||
To use any widget, you must first define a `widget_context_t`. This structure contains function pointers for basic drawing operations (rects, strings) and theme preferences.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
void *user_data;
|
||||
void (*draw_rect)(void *user_data, int x, int y, int w, int h, uint32_t color);
|
||||
void (*draw_rounded_rect_filled)(void *user_data, int x, int y, int w, int h, int r, uint32_t color);
|
||||
void (*draw_string)(void *user_data, int x, int y, const char *str, uint32_t color);
|
||||
int (*measure_string_width)(void *user_data, const char *str);
|
||||
void (*mark_dirty)(void *user_data, int x, int y, int w, int h);
|
||||
bool use_light_theme;
|
||||
} widget_context_t;
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Usually, `user_data` is set to your `ui_window_t` handle, and the functions are simple wrappers around `ui_draw_rect`, `ui_draw_string`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Button (`widget_button_t`)
|
||||
|
||||
Standard interactive button with hover and click states.
|
||||
|
||||
* `void widget_button_init(widget_button_t *btn, int x, int y, int w, int h, const char *text);`
|
||||
* `void widget_button_draw(widget_context_t *ctx, widget_button_t *btn);`
|
||||
* `bool widget_button_handle_mouse(widget_button_t *btn, int mx, int my, bool mouse_down, bool mouse_clicked, void *user_data);`
|
||||
|
||||
### Usage Example:
|
||||
```c
|
||||
widget_button_t my_btn;
|
||||
widget_button_init(&my_btn, 10, 10, 80, 25, "Click Me");
|
||||
my_btn.on_click = my_callback_func;
|
||||
|
||||
// In your event loop:
|
||||
widget_button_handle_mouse(&my_btn, ev.arg1, ev.arg2, is_down, is_clicked, my_data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scrollbar (`widget_scrollbar_t`)
|
||||
|
||||
Vertical scrollbar supporting dragging and track-paging.
|
||||
|
||||
* `void widget_scrollbar_init(widget_scrollbar_t *sb, int x, int y, int w, int h);`
|
||||
* `void widget_scrollbar_update(widget_scrollbar_t *sb, int content_height, int scroll_y);`
|
||||
* `void widget_scrollbar_draw(widget_context_t *ctx, widget_scrollbar_t *sb);`
|
||||
* `bool widget_scrollbar_handle_mouse(widget_scrollbar_t *sb, int mx, int my, bool mouse_down, void *user_data);`
|
||||
|
||||
> [!NOTE]
|
||||
> The scrollbar automatically calculates the "thumb" size based on the ratio of `h` to `content_height`.
|
||||
|
||||
---
|
||||
|
||||
## TextBox (`widget_textbox_t`)
|
||||
|
||||
Editable text field with focus support and keyboard handling.
|
||||
|
||||
* `void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);`
|
||||
* `void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);`
|
||||
* `bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||
* `bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);`
|
||||
|
||||
---
|
||||
|
||||
## Dropdown (`widget_dropdown_t`)
|
||||
|
||||
Selection menu for picking one item from a list.
|
||||
|
||||
* `void widget_dropdown_init(widget_dropdown_t *dd, int x, int y, int w, int h, const char **items, int count);`
|
||||
* `void widget_dropdown_draw(widget_context_t *ctx, widget_dropdown_t *dd);`
|
||||
* `bool widget_dropdown_handle_mouse(widget_dropdown_t *dd, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||
|
||||
---
|
||||
|
||||
## Checkbox / Radio (`widget_checkbox_t`)
|
||||
|
||||
Toggleable options with support for circular "Radio" style or square "Checkbox" style.
|
||||
|
||||
* `void widget_checkbox_init(widget_checkbox_t *cb, int x, int y, int w, int h, const char *text, bool is_radio);`
|
||||
* `void widget_checkbox_draw(widget_context_t *ctx, widget_checkbox_t *cb);`
|
||||
* `bool widget_checkbox_handle_mouse(widget_checkbox_t *cb, int mx, int my, bool mouse_clicked, void *user_data);`
|
||||
|
||||
---
|
||||
|
||||
## Event Integration
|
||||
|
||||
Widgets are designed to be polled within your `libui` event loop. Most handle-mouse functions return `true` if the event was "consumed" by the widget, allowing you to stop further processing for that event.
|
||||
|
||||
```c
|
||||
if (ui_get_event(win, &ev)) {
|
||||
bool handled = false;
|
||||
handled |= widget_button_handle_mouse(&btn, ev.arg1, ev.arg2, is_down, is_clicked, NULL);
|
||||
if (!handled) {
|
||||
// Handle global window events...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -9,7 +9,7 @@ BoredOS is a 64-bit hobbyist operating system designed for the x86_64 architectu
|
||||
|
||||
This document serves as an overview of the core architecture and the layout of the kernel source code.
|
||||
|
||||
## 📂 Source Code Layout (`src/`)
|
||||
## Source Code Layout (`src/`)
|
||||
|
||||
The OS heavily relies on module separation. The `src/` directory is logically split into several domains:
|
||||
|
||||
@@ -23,7 +23,7 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
||||
- **`wm/`**: The graphical subsystem. It handles drawing primitives, window structures, font rendering, and double-buffering.
|
||||
- **`userland/`**: Out-of-kernel components. This includes the custom SDK/compiler environment (`libc/`) and user applications (`cli/`, `gui/`, `games/`).
|
||||
|
||||
## 🚀 Boot Process
|
||||
## Boot Process
|
||||
|
||||
BoredOS uses **Limine** as its primary bootloader.
|
||||
|
||||
@@ -42,7 +42,7 @@ BoredOS utilizes Symmetric Multi-Processing (SMP) to distribute workloads across
|
||||
- **Scheduler**: A round-robin scheduler runs on each core. Processes are pinned to specific CPUs (CPU Affinity) to maintain cache locality. The BSP timer interrupt (`60Hz`) broadcasts a scheduling IPI to all core to ensure balanced execution.
|
||||
- **Spinlocks**: Since multiple cores can access kernel structures (VFS, Process List) simultaneously, the kernel uses **interrupt-safe spinlocks** to prevent race conditions.
|
||||
|
||||
## 🛡️ Userland Transition
|
||||
## Userland Transition
|
||||
|
||||
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application is launched, the kernel:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
BoredOS implements a rudimentary but functional filesystem layer designed to support reading system assets and user applications during runtime.
|
||||
|
||||
## 🗂️ Virtual File System (VFS)
|
||||
## Virtual File System (VFS)
|
||||
|
||||
The Virtual File System acts as an abstraction layer across different underlying storage mechanisms (even if, currently, only one type is fully utilized). System calls targeting files (`SYS_FS`) route through the VFS rather than interacting with the disk directly.
|
||||
|
||||
@@ -17,11 +17,11 @@ Key VFS functionalities include:
|
||||
- **Path Parsing**: Resolving absolute and relative paths.
|
||||
- **SMP Safety**: All VFS and underlying FAT32 operations are protected by a global **Spinlock**. This ensures that multiple cores can safely read from the filesystem simultaneously without corrupting internal file seek pointers or directory cache states.
|
||||
|
||||
## 💾 FAT32 Implementation
|
||||
## FAT32 Implementation
|
||||
|
||||
The primary filesystem logic in `fat32.c` handles both in-memory RAM-based filesystem simulation and physical ATA block devices.
|
||||
|
||||
### 💿 Storage Support
|
||||
### Storage Support
|
||||
|
||||
BoredOS supports two main types of storage for its FAT32 implementation:
|
||||
|
||||
@@ -30,7 +30,7 @@ BoredOS supports two main types of storage for its FAT32 implementation:
|
||||
- **GPT is NOT supported**: Currently, only **MBR (Master Boot Record)** partition tables or **raw (partitionless)** disks are supported.
|
||||
- **Filesystem**: The partition must be formatted as **FAT32**.
|
||||
|
||||
### 🔍 Auto-detection
|
||||
### Auto-detection
|
||||
The `Disk Manager` automatically probes primary and secondary IDE channels during initialization. If a valid FAT32 partition is found (either directly at sector 0 or via an MBR partition table), the disk is assigned a drive letter (starting from `B:`) and becomes accessible to the VFS.
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
Memory management in BoredOS is split into physical and virtual layers, designed to support both kernel operations and userland isolation on the x86_64 architecture.
|
||||
|
||||
## 🧠 Physical Memory Management (PMM)
|
||||
## Physical Memory Management (PMM)
|
||||
|
||||
The PMM is responsible for tracking which physical RAM frames (usually 4KB each) are free and which are in use.
|
||||
|
||||
@@ -19,7 +19,7 @@ The PMM is responsible for tracking which physical RAM frames (usually 4KB each)
|
||||
> [!NOTE]
|
||||
> 4KB frame sizes strike a balance between allocation speed and minimal memory fragmentation, fitting directly with the page tables.
|
||||
|
||||
## 🗺️ Virtual Memory Management (VMM) and Paging
|
||||
## Virtual Memory Management (VMM) and Paging
|
||||
|
||||
BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing the virtual address space between the kernel and userland.
|
||||
|
||||
@@ -30,7 +30,7 @@ BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing
|
||||
- **User Space**: Userland applications are loaded into lower virtual addresses.
|
||||
- **Page Faults**: The `mem/` subsystem registers an Interrupt Service Routine (ISR) for page faults (Interrupt 14). If a process accesses unmapped memory, the handler determines whether to allocate a new frame or terminate the process.
|
||||
|
||||
## 🏗️ Kernel Heap
|
||||
## Kernel Heap
|
||||
|
||||
Dynamic allocation within the kernel (`kmalloc` and `kfree`) is layered on top of the physical allocator. The kernel maintains its own heap area in virtual memory. When the heap requires more space, it requests physical frames from the PMM and maps them into the kernel's virtual address space.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
BoredOS features a fully custom, graphical Window Manager built directly into the kernel, residing in the `src/wm/` directory. It is responsible for compositing the screen, handling window logic, rendering text, and dispatching UI events.
|
||||
|
||||
## 🖼️ Framebuffer and Rendering
|
||||
## Framebuffer and Rendering
|
||||
|
||||
1. **Limine Framebuffer**: During boot, the Limine bootloader requests a graphical framebuffer from the hardware (e.g., GOP in UEFI environments) and passes a pointer to this linear memory buffer to the kernel.
|
||||
2. **Double Buffering**: To prevent screen tearing, the WM does not draw directly to the screen. It allocates a "back buffer" in kernel memory equal to the size of the screen. All drawing operations (lines, rectangles, windows) happen on this back buffer.
|
||||
@@ -24,7 +24,7 @@ The windowing system is built around a linked list of `Window` structures.
|
||||
- **Window Structures**: Each window object tracks its dimensions (`x`, `y`, `width`, `height`), title, background color, and an internal buffer if it's acting as a canvas for userland apps.
|
||||
- **Decorations**: The kernel handles drawing window borders, title bars, and close buttons automatically unless a borderless style is specified.
|
||||
|
||||
## 🖱️ Input Handling and Events
|
||||
## Input Handling and Events
|
||||
|
||||
The WM acts as the central hub for input routing.
|
||||
|
||||
|
||||
33
docs/usage/booting.md
Normal file
33
docs/usage/booting.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Booting BoredOS
|
||||
|
||||
BoredOS uses the Limine bootloader, which provides a flexible way to configure the boot process and pass parameters to the kernel.
|
||||
|
||||
## Boot Parameters
|
||||
|
||||
You can modify system behavior at startup by passing specific boot flags.
|
||||
|
||||
### Verbose Boot (`-v`)
|
||||
|
||||
The `-v` flag enables the kernel console (`kconsole`) during the boot process. When enabled, the kernel will display detailed initialization logs on the screen. By default, this is often disabled in the included configuration for a cleaner "splash-only" boot experience.
|
||||
|
||||
#### Toggling Verbose Boot at Runtime
|
||||
|
||||
You can enable or disable the verbose boot log directly from the Limine boot menu without modifying the source files:
|
||||
|
||||
1. **Select Entry**: When the Limine boot menu appears, highlight the **BoredOS** entry.
|
||||
2. **Edit**: Press `E` to enter the entry editor.
|
||||
3. **Modify Flag**: Find the line containing `cmdline: -v`.
|
||||
- To **Enable**: Remove the `#` character if the line is commented out (change `# cmdline: -v` to `cmdline: -v`).
|
||||
- To **Disable**: Add a `# ` at the start of the line.
|
||||
4. **Boot**: Press `F10` to boot using the modified parameters.
|
||||
|
||||
#### Persistent Configuration
|
||||
|
||||
To change the default behavior permanently, modify the `limine.conf` file in the repository root before building the ISO:
|
||||
|
||||
```conf
|
||||
/BoredOS
|
||||
protocol: limine
|
||||
path: boot():/boredos.elf
|
||||
cmdline: -v
|
||||
```
|
||||
35
docs/usage/desktop.md
Normal file
35
docs/usage/desktop.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Using the Desktop
|
||||
|
||||
The BoredOS desktop environment is designed to be intuitive while providing powerful window management and icons for quick access to your files and applications.
|
||||
|
||||
## Window Management
|
||||
|
||||
BoredOS uses a stacking window manager (BoredWM) that allows you to overlap and organize multiple windows.
|
||||
|
||||
### Basic Actions
|
||||
- **Focus**: Click anywhere on a window to bring it to the front and make it the active window.
|
||||
- **Move**: Click and drag the **title bar** (the top bar of the window) to reposition it on the screen.
|
||||
- **Close**: Click the red traffic light close button in the top-left corner of the window.
|
||||
|
||||
### System-wide Shortcuts
|
||||
BoredOS includes several global shortcuts to help you manage your workflow:
|
||||
- **`Ctrl + P`**: Take a screenshot. The image will be saved to `/root/Desktop` as `screenshot.jpg`.
|
||||
- **`Shift + Ctrl + Space`**: Toggle **Lumos** search (see the [Lumos guide](lumos.md)).
|
||||
|
||||
## Desktop Icons
|
||||
|
||||
Your desktop represents the contents of the `/root/Desktop` directory.
|
||||
|
||||
- **Launching**: Double-click an icon to open the file or launch the application.
|
||||
- **Snapping**: Icons automatically snap to a grid for a clean look. You can toggle "Snap to Grid" and "Auto Align" in the [Settings app](../launching_apps.md).
|
||||
- **Context Menu**: Right-click on the desktop background to create new files, folders, or refresh the layout.
|
||||
|
||||
## The Bottom Dock
|
||||
|
||||
The dock at the bottom of the screen provides quick shortcuts to your most-used applications, with for example:
|
||||
- **Files**: Browse the entire filesystem.
|
||||
- **Terminal**: Access the command-line interface.
|
||||
- **Calculator / Notepad / Grapher**: Essential productivity tools.
|
||||
|
||||
---
|
||||
[Return to Documentation Index](../README.md)
|
||||
31
docs/usage/launching_apps.md
Normal file
31
docs/usage/launching_apps.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Launching Applications
|
||||
|
||||
BoredOS provides several ways to launch applications and files, depending on your preferred workflow.
|
||||
|
||||
## 1. Using the File Explorer
|
||||
|
||||
The File Explorer is the primary way to navigate the filesystem and launch any `.elf` binary or associated document.
|
||||
|
||||
1. Open the **Explorer** from the dock or desktop.
|
||||
2. Navigate to `/bin` for system applications or your own user folders.
|
||||
3. **Double-click** any executable (`.elf`) to run it.
|
||||
4. Standard files (like `.jpg` or `.txt`) will automatically open in their default viewer.
|
||||
|
||||
## 2. Desktop Shortcuts and Icons
|
||||
|
||||
Commonly used applications are placed directly on the desktop.
|
||||
|
||||
- Simply **Double-click** any icon on the desktop to launch it.
|
||||
- You can also create desktop shortcuts by right-clicking on a file and selecting **"Create Shortcut"**.
|
||||
|
||||
## 3. Using Lumos (Global Search)
|
||||
|
||||
For the fastest access, use **Lumos** to search and launch by name:
|
||||
|
||||
1. Press **`Shift + Ctrl + Space`**.
|
||||
2. Type the name of the app (e.g., "DOOM.elf").
|
||||
3. Press **Enter** to launch.
|
||||
|
||||
|
||||
---
|
||||
[Return to Documentation Index](../README.md)
|
||||
29
docs/usage/lumos.md
Normal file
29
docs/usage/lumos.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Lumos: System Search
|
||||
|
||||
**Lumos** is the powerful, system-wide search and launch assistant for BoredOS. It allows you to find applications, documents, and system files instantly without navigating through folders.
|
||||
|
||||
## Opening Lumos
|
||||
|
||||
To activate Lumos at any time, use the global keyboard shortcut:
|
||||
|
||||
**`Shift + Ctrl + Space`**
|
||||
|
||||
The Lumos search modal will appear in the center of your screen, ready for input.
|
||||
|
||||
## Features
|
||||
|
||||
- **Fuzzy Searching**: You don't need to type the exact name. Lumos uses fuzzy matching to find the most relevant results as you type.
|
||||
- **Deep Indexing**: Lumos indexes files across the entire system.
|
||||
- **Quick Launch**: Once you find what you're looking for, launching it is as simple as pressing `Enter`.
|
||||
|
||||
## Navigation
|
||||
|
||||
When the Lumos window is open:
|
||||
- **Type**: Just start typing to filter results.
|
||||
- **Arrow Keys (Up/Down)**: Move the selection highlight through the list of results.
|
||||
- **Enter**: Launch the selected file or application.
|
||||
- **Backspace**: Delete characters in your search query.
|
||||
- **Escape**: Close Lumos and return to the desktop.
|
||||
|
||||
---
|
||||
[Return to Documentation Index](../README.md)
|
||||
60
docs/usage/terminal.md
Normal file
60
docs/usage/terminal.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Terminal & Command Line
|
||||
|
||||
The BoredOS Terminal provides a powerful command-line interface (CLI) for advanced users and developers. It supports standard Unix-like features and provides direct access to the kernel's system calls.
|
||||
|
||||
## The Shell
|
||||
|
||||
The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features:
|
||||
- **ANSI Color Support**: Rich text output with colors and styles.
|
||||
- **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries).
|
||||
- **Output Redirection**:
|
||||
- `command > file`: Write output to a new file (or overwrite existing).
|
||||
- `command >> file`: Append output to an existing file.
|
||||
- **Piping**:
|
||||
- `command1 | command2`: Pass the output of the first command as input to the second.
|
||||
|
||||
### Bsh Configuration
|
||||
|
||||
Bsh loads its configuration from:
|
||||
|
||||
`/Library/bsh/bshrc`
|
||||
|
||||
This file is similar to `.zshrc` and can define:
|
||||
- `PATH` for command lookup
|
||||
- `STARTUP` for interactive shell startup scripts
|
||||
- `BOOT_SCRIPT` for a once-per-boot script
|
||||
- prompt templates (`PROMPT_LEFT`, `PROMPT_RIGHT`)
|
||||
|
||||
Prompt tokens:
|
||||
- `%n` username
|
||||
- `%h` hostname
|
||||
- `%~` cwd ("~" for `/root`)
|
||||
- `%T` time (HH:MM)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
PATH=/bin:/root/Apps
|
||||
PROMPT_LEFT=%n@%h:%~$
|
||||
STARTUP=/Library/bsh/startup.bsh
|
||||
BOOT_SCRIPT=/Library/bsh/boot.bsh
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
|
||||
Below are some of the most used commands available in `/bin`:
|
||||
|
||||
| Command | Description |
|
||||
| :--- | :--- |
|
||||
| `ls` | List files and directories in the current path. |
|
||||
| `cd` | Change the current working directory. |
|
||||
| `cat` | Display the contents of a file. |
|
||||
| `ls` | List directory contents. |
|
||||
| `rm` | Remove a file. |
|
||||
| `mkdir` | Create a new directory. |
|
||||
| `man` | View the manual for a specific command (e.g., `man ls`). |
|
||||
| `sysfetch` | Display system and hardware information. |
|
||||
|
||||
|
||||
---
|
||||
[Return to Documentation Index](../README.md)
|
||||
@@ -13,3 +13,4 @@ backdrop: 000000
|
||||
/BoredOS
|
||||
protocol: limine
|
||||
path: boot():/boredos.elf
|
||||
cmdline: -v
|
||||
|
||||
@@ -45,7 +45,11 @@ isr%2_wrapper:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||
test qword [rsp + 144], 3
|
||||
jz %%skip_swap
|
||||
swapgs
|
||||
%%skip_swap:
|
||||
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
@@ -76,6 +80,12 @@ isr%2_wrapper:
|
||||
pop rcx
|
||||
pop rbx
|
||||
pop rax
|
||||
|
||||
test qword [rsp + 24], 3
|
||||
jz %%skip_swap_back
|
||||
swapgs
|
||||
%%skip_swap_back:
|
||||
|
||||
add rsp, 16 ; drop dummy vector and error code
|
||||
iretq
|
||||
%endmacro
|
||||
@@ -164,7 +174,11 @@ exception_common:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state (fxsave requires 16-byte alignment)
|
||||
test qword [rsp + 144], 3
|
||||
jz .skip_swap_exc
|
||||
swapgs
|
||||
.skip_swap_exc:
|
||||
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
@@ -196,6 +210,12 @@ exception_common:
|
||||
pop rcx
|
||||
pop rbx
|
||||
pop rax
|
||||
|
||||
test qword [rsp + 24], 3
|
||||
jz .skip_swap_back_exc
|
||||
swapgs
|
||||
.skip_swap_back_exc:
|
||||
|
||||
add rsp, 16 ; drop vector and error code
|
||||
iretq
|
||||
|
||||
|
||||
@@ -15,15 +15,14 @@ section .text
|
||||
; R9 = arg5
|
||||
|
||||
syscall_entry:
|
||||
; 1. Switch to Kernel Stack safely
|
||||
; Note: For true SMP safety, we need per-CPU storage (via swapgs).
|
||||
; For now, we use a global scratch which is only safe because we mask interrupts on entry.
|
||||
mov [rel user_rsp_scratch], rsp
|
||||
mov rsp, [rel kernel_syscall_stack]
|
||||
swapgs
|
||||
|
||||
; 2. Build iretq frame (compatible with registers_t)
|
||||
mov [gs:40], rsp
|
||||
mov rsp, [gs:48]
|
||||
|
||||
; 2. Build iretq frame
|
||||
push 0x1B ; SS (User Data)
|
||||
push qword [rel user_rsp_scratch] ; RSP
|
||||
push qword [gs:40] ; RSP
|
||||
push r11 ; RFLAGS (captured by syscall)
|
||||
push 0x23 ; CS (User Code)
|
||||
push rcx ; RIP (return address from syscall)
|
||||
@@ -81,14 +80,7 @@ syscall_entry:
|
||||
pop rax
|
||||
add rsp, 16 ; drop int_no/err_code
|
||||
|
||||
; Debug: check RIP before iretq
|
||||
; We can't easily print from here without destroying registers,
|
||||
; but we can at least check if it's canonical.
|
||||
|
||||
swapgs
|
||||
iretq
|
||||
|
||||
section .bss
|
||||
global kernel_syscall_stack
|
||||
global user_rsp_scratch
|
||||
kernel_syscall_stack: resq 1
|
||||
user_rsp_scratch: resq 1
|
||||
|
||||
95
src/core/kconsole.c
Normal file
95
src/core/kconsole.c
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "kconsole.h"
|
||||
#include "graphics.h"
|
||||
#include "sys/spinlock.h"
|
||||
#include <stddef.h>
|
||||
|
||||
static spinlock_t console_lock = SPINLOCK_INIT;
|
||||
static int cursor_x = 0;
|
||||
static int cursor_y = 0;
|
||||
static bool kconsole_active = false;
|
||||
static uint32_t text_color = 0xFFFFFFFF; // White
|
||||
|
||||
#define CHAR_WIDTH 8
|
||||
#define CHAR_HEIGHT 10
|
||||
|
||||
void kconsole_init(void) {
|
||||
cursor_x = 10;
|
||||
cursor_y = 10;
|
||||
kconsole_active = false;
|
||||
|
||||
// Initial clear screen during boot
|
||||
graphics_clear_back_buffer(0x00000000);
|
||||
graphics_mark_screen_dirty();
|
||||
graphics_flip_buffer();
|
||||
}
|
||||
|
||||
void kconsole_set_active(bool active) {
|
||||
kconsole_active = active;
|
||||
}
|
||||
|
||||
void kconsole_set_color(uint32_t color) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&console_lock);
|
||||
text_color = color;
|
||||
spinlock_release_irqrestore(&console_lock, flags);
|
||||
}
|
||||
|
||||
static void kconsole_scroll(void) {
|
||||
if (cursor_y + CHAR_HEIGHT >= get_screen_height() - 10) {
|
||||
graphics_scroll_back_buffer(CHAR_HEIGHT);
|
||||
cursor_y -= CHAR_HEIGHT;
|
||||
graphics_mark_screen_dirty();
|
||||
graphics_flip_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
static void kconsole_putc_nolock(char c) {
|
||||
if (!kconsole_active) return;
|
||||
|
||||
if (c == '\n') {
|
||||
cursor_x = 10;
|
||||
cursor_y += CHAR_HEIGHT;
|
||||
kconsole_scroll();
|
||||
graphics_flip_buffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\r') {
|
||||
cursor_x = 10;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\t') {
|
||||
cursor_x += CHAR_WIDTH * 4;
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw character
|
||||
draw_char_bitmap(cursor_x, cursor_y, c, text_color);
|
||||
graphics_mark_screen_dirty();
|
||||
|
||||
cursor_x += CHAR_WIDTH;
|
||||
if (cursor_x + CHAR_WIDTH >= get_screen_width() - 10) {
|
||||
cursor_x = 10;
|
||||
cursor_y += CHAR_HEIGHT;
|
||||
kconsole_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
void kconsole_putc(char c) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&console_lock);
|
||||
kconsole_putc_nolock(c);
|
||||
spinlock_release_irqrestore(&console_lock, flags);
|
||||
}
|
||||
|
||||
void kconsole_write(const char *s) {
|
||||
if (!s) return;
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&console_lock);
|
||||
while (*s) {
|
||||
kconsole_putc_nolock(*s++);
|
||||
}
|
||||
|
||||
// Flip buffer after a write batch during boot
|
||||
graphics_flip_buffer();
|
||||
spinlock_release_irqrestore(&console_lock, flags);
|
||||
}
|
||||
13
src/core/kconsole.h
Normal file
13
src/core/kconsole.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef KCONSOLE_H
|
||||
#define KCONSOLE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void kconsole_init(void);
|
||||
void kconsole_set_color(uint32_t color);
|
||||
void kconsole_putc(char c);
|
||||
void kconsole_write(const char *s);
|
||||
void kconsole_set_active(bool active);
|
||||
|
||||
#endif // KCONSOLE_H
|
||||
@@ -30,6 +30,16 @@ int k_strcmp(const char *s1, const char *s2) {
|
||||
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
|
||||
}
|
||||
|
||||
int k_strncmp(const char *s1, const char *s2, size_t n) {
|
||||
while (n && *s1 && (*s1 == *s2)) {
|
||||
s1++;
|
||||
s2++;
|
||||
n--;
|
||||
}
|
||||
if (n == 0) return 0;
|
||||
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
|
||||
}
|
||||
|
||||
void k_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
@@ -145,3 +155,17 @@ void k_beep_process(void) {
|
||||
}
|
||||
}
|
||||
|
||||
char *k_strstr(const char *haystack, const char *needle) {
|
||||
if (!*needle) return (char *)haystack;
|
||||
for (; *haystack; haystack++) {
|
||||
const char *h = haystack;
|
||||
const char *n = needle;
|
||||
while (*h && *n && *h == *n) {
|
||||
h++;
|
||||
n++;
|
||||
}
|
||||
if (!*n) return (char *)haystack;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ void k_memset(void *dest, int val, size_t len);
|
||||
void k_memcpy(void *dest, const void *src, size_t len);
|
||||
size_t k_strlen(const char *str);
|
||||
int k_strcmp(const char *s1, const char *s2);
|
||||
int k_strncmp(const char *s1, const char *s2, size_t n);
|
||||
void k_strcpy(char *dest, const char *src);
|
||||
int k_atoi(const char *str);
|
||||
void k_itoa(int n, char *buf);
|
||||
@@ -25,5 +26,6 @@ void k_reboot(void);
|
||||
void k_shutdown(void);
|
||||
void k_beep(int freq, int ms);
|
||||
void k_beep_process(void);
|
||||
char *k_strstr(const char *haystack, const char *needle);
|
||||
|
||||
#endif
|
||||
|
||||
219
src/core/main.c
219
src/core/main.c
@@ -17,12 +17,23 @@
|
||||
#include "io.h"
|
||||
#include "fat32.h"
|
||||
#include "tar.h"
|
||||
#include "vfs.h"
|
||||
#include "core/kconsole.h"
|
||||
#include "core/kutils.h"
|
||||
#include "memory_manager.h"
|
||||
#include "platform.h"
|
||||
#include "wallpaper.h"
|
||||
#include "smp.h"
|
||||
#include "work_queue.h"
|
||||
#include "lapic.h"
|
||||
#include "fs/sysfs.h"
|
||||
#include "fs/procfs.h"
|
||||
#include "fs/bootfs.h"
|
||||
#include "sys/kernel_subsystem.h"
|
||||
#include "sys/module_manager.h"
|
||||
#include "sys/bootfs_state.h"
|
||||
|
||||
extern void sysfs_init_subsystems(void);
|
||||
|
||||
// --- Limine Requests ---
|
||||
__attribute__((used, section(".requests")))
|
||||
@@ -53,12 +64,26 @@ static volatile struct limine_smp_request smp_request = {
|
||||
.flags = 0
|
||||
};
|
||||
|
||||
__attribute__((used, section(".requests")))
|
||||
static volatile struct limine_bootloader_info_request bootloader_info_request = {
|
||||
.id = LIMINE_BOOTLOADER_INFO_REQUEST,
|
||||
.revision = 0
|
||||
};
|
||||
|
||||
__attribute__((used, section(".requests")))
|
||||
static volatile struct limine_kernel_file_request kernel_file_request = {
|
||||
.id = LIMINE_KERNEL_FILE_REQUEST,
|
||||
.revision = 0
|
||||
};
|
||||
|
||||
__attribute__((used, section(".requests_start")))
|
||||
static volatile struct limine_request *const requests_start_marker[] = {
|
||||
(struct limine_request *)&framebuffer_request,
|
||||
(struct limine_request *)&memmap_request,
|
||||
(struct limine_request *)&module_request,
|
||||
(struct limine_request *)&smp_request,
|
||||
(struct limine_request *)&bootloader_info_request,
|
||||
(struct limine_request *)&kernel_file_request,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -84,26 +109,70 @@ static void init_serial() {
|
||||
outb(0x3F8 + 4, 0x0B);
|
||||
}
|
||||
|
||||
static spinlock_t serial_lock = SPINLOCK_INIT;
|
||||
|
||||
void serial_write(const char *str) {
|
||||
while (*str) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&serial_lock);
|
||||
const char *p = str;
|
||||
while (*p) {
|
||||
char c = *p++;
|
||||
while ((inb(0x3F8 + 5) & 0x20) == 0);
|
||||
outb(0x3F8, *str++);
|
||||
outb(0x3F8, c);
|
||||
}
|
||||
kconsole_write(str);
|
||||
spinlock_release_irqrestore(&serial_lock, flags);
|
||||
}
|
||||
|
||||
static void serial_write_num_locked(uint32_t n) {
|
||||
if (n >= 10) serial_write_num_locked(n / 10);
|
||||
char c = '0' + (n % 10);
|
||||
while ((inb(0x3F8 + 5) & 0x20) == 0);
|
||||
outb(0x3F8, c);
|
||||
kconsole_putc(c);
|
||||
}
|
||||
|
||||
void serial_write_num(uint32_t n) {
|
||||
if (n >= 10) serial_write_num(n / 10);
|
||||
uint64_t flags = spinlock_acquire_irqsave(&serial_lock);
|
||||
serial_write_num_locked(n);
|
||||
spinlock_release_irqrestore(&serial_lock, flags);
|
||||
}
|
||||
|
||||
static void serial_write_hex_locked(uint64_t n) {
|
||||
char *hex = "0123456789ABCDEF";
|
||||
if (n >= 16) serial_write_hex_locked(n / 16);
|
||||
char c = hex[n % 16];
|
||||
while ((inb(0x3F8 + 5) & 0x20) == 0);
|
||||
outb(0x3F8, '0' + (n % 10));
|
||||
outb(0x3F8, c);
|
||||
kconsole_putc(c);
|
||||
}
|
||||
|
||||
void serial_write_hex(uint64_t n) {
|
||||
char *hex = "0123456789ABCDEF";
|
||||
if (n >= 16) serial_write_hex(n / 16);
|
||||
while ((inb(0x3F8 + 5) & 0x20) == 0);
|
||||
outb(0x3F8, hex[n % 16]);
|
||||
uint64_t flags = spinlock_acquire_irqsave(&serial_lock);
|
||||
serial_write_hex_locked(n);
|
||||
spinlock_release_irqrestore(&serial_lock, flags);
|
||||
}
|
||||
|
||||
void log_ok(const char *msg) {
|
||||
serial_write("[ ");
|
||||
kconsole_set_color(0xFF00FF00);
|
||||
serial_write("OK");
|
||||
kconsole_set_color(0xFFFFFFFF);
|
||||
serial_write(" ] ");
|
||||
serial_write(msg);
|
||||
serial_write("\n");
|
||||
}
|
||||
|
||||
void log_fail(const char *msg) {
|
||||
serial_write("[ ");
|
||||
kconsole_set_color(0xFFFF0000);
|
||||
serial_write("FAIL");
|
||||
kconsole_set_color(0xFFFFFFFF);
|
||||
serial_write(" ] ");
|
||||
serial_write(msg);
|
||||
serial_write("\n");
|
||||
}
|
||||
|
||||
|
||||
// Kernel Entry Point
|
||||
|
||||
static void fat32_mkdir_recursive(const char *path) {
|
||||
@@ -133,62 +202,74 @@ static void fat32_mkdir_recursive(const char *path) {
|
||||
|
||||
void kmain(void) {
|
||||
init_serial();
|
||||
serial_write("\n[DEBUG] Entering kmain...\n");
|
||||
vfs_init();
|
||||
serial_write("\n");
|
||||
|
||||
platform_init();
|
||||
serial_write("[DEBUG] platform_init OK\n");
|
||||
log_ok("Platform initialized");
|
||||
|
||||
extern uint64_t hhdm_offset;
|
||||
extern uint64_t kernel_phys_base;
|
||||
extern uint64_t kernel_virt_base;
|
||||
|
||||
serial_write("[DEBUG] HHDM Offset: 0x");
|
||||
serial_write("[INIT] HHDM Offset: 0x");
|
||||
serial_write_hex(hhdm_offset);
|
||||
serial_write("\n");
|
||||
serial_write("[DEBUG] Kernel Phys: 0x");
|
||||
serial_write("[INIT] Kernel Phys: 0x");
|
||||
serial_write_hex(kernel_phys_base);
|
||||
serial_write("\n");
|
||||
serial_write("[DEBUG] Kernel Virt: 0x");
|
||||
serial_write("[INIT] Kernel Virt: 0x");
|
||||
serial_write_hex(kernel_virt_base);
|
||||
serial_write("\n");
|
||||
|
||||
if (memmap_request.response != NULL) {
|
||||
// The memory manager will now scan the memory map and manage all usable regions.
|
||||
memory_manager_init_from_memmap(memmap_request.response);
|
||||
serial_write("[DEBUG] memory_manager_init OK\n");
|
||||
} else {
|
||||
serial_write("[DEBUG] ERROR: No usable memory for heap! Check Limine memmap.\n");
|
||||
hcf();
|
||||
}
|
||||
|
||||
if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) {
|
||||
serial_write("[DEBUG] No framebuffer! Halting.\n");
|
||||
serial_write("[INIT] No framebuffer! Halting.\n");
|
||||
hcf();
|
||||
}
|
||||
|
||||
struct limine_framebuffer *fb = framebuffer_request.response->framebuffers[0];
|
||||
graphics_init(fb);
|
||||
serial_write("[DEBUG] graphics_init OK\n");
|
||||
kconsole_init();
|
||||
|
||||
// Check for verbose boot flag
|
||||
if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) {
|
||||
const char *cmdline = kernel_file_request.response->kernel_file->cmdline;
|
||||
if (cmdline != NULL && k_strstr(cmdline, "-v") != NULL) {
|
||||
kconsole_set_active(true);
|
||||
}
|
||||
}
|
||||
|
||||
log_ok("Graphics and Console ready");
|
||||
|
||||
if (memmap_request.response != NULL) {
|
||||
memory_manager_init_from_memmap(memmap_request.response);
|
||||
log_ok("Memory manager ready");
|
||||
smp_init_bsp();
|
||||
log_ok("SMP BSP initialized");
|
||||
} else {
|
||||
log_fail("No usable memory for heap! Check Limine memmap.");
|
||||
hcf();
|
||||
}
|
||||
|
||||
gdt_init();
|
||||
serial_write("[DEBUG] gdt_init OK\n");
|
||||
log_ok("GDT initialized");
|
||||
|
||||
paging_init();
|
||||
serial_write("[DEBUG] paging_init OK\n");
|
||||
log_ok("Paging ready");
|
||||
|
||||
syscall_init();
|
||||
serial_write("[DEBUG] syscall_init OK\n");
|
||||
log_ok("Syscalls ready");
|
||||
|
||||
idt_init();
|
||||
idt_register_interrupts();
|
||||
idt_load();
|
||||
serial_write("[DEBUG] idt_init OK\n");
|
||||
log_ok("IDT ready");
|
||||
|
||||
process_init();
|
||||
|
||||
|
||||
fat32_init();
|
||||
serial_write("[DEBUG] fat32_init OK\n");
|
||||
log_ok("FAT32 ready");
|
||||
fat32_mkdir("/bin");
|
||||
fat32_mkdir("/Library");
|
||||
fat32_mkdir("/Library/images");
|
||||
@@ -196,14 +277,68 @@ void kmain(void) {
|
||||
fat32_mkdir("/Library/images/gif");
|
||||
fat32_mkdir("/Library/Fonts");
|
||||
fat32_mkdir("/Library/DOOM");
|
||||
fat32_mkdir("/Library/bsh");
|
||||
fat32_mkdir("/docs");
|
||||
|
||||
sysfs_init_subsystems();
|
||||
vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL);
|
||||
vfs_mount("/proc", "procfs", "procfs", procfs_get_ops(), NULL);
|
||||
|
||||
bootfs_init();
|
||||
|
||||
if (bootloader_info_request.response != NULL) {
|
||||
if (bootloader_info_request.response->name) {
|
||||
k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name);
|
||||
}
|
||||
if (bootloader_info_request.response->version) {
|
||||
k_strcpy(g_bootfs_state.bootloader_version, bootloader_info_request.response->version);
|
||||
}
|
||||
}
|
||||
|
||||
if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) {
|
||||
g_bootfs_state.kernel_size = kernel_file_request.response->kernel_file->size;
|
||||
serial_write("[INIT] Kernel size from bootloader: ");
|
||||
serial_write_hex(g_bootfs_state.kernel_size);
|
||||
serial_write(" bytes\n");
|
||||
}
|
||||
|
||||
extern uint32_t wm_get_ticks(void);
|
||||
g_bootfs_state.boot_time_ms = wm_get_ticks();
|
||||
|
||||
if (module_request.response != NULL) {
|
||||
g_bootfs_state.num_modules = module_request.response->module_count;
|
||||
|
||||
serial_write("[INIT] Scanning modules for bootfs state...\n");
|
||||
for (uint64_t i = 0; i < module_request.response->module_count; i++) {
|
||||
struct limine_file *mod = module_request.response->modules[i];
|
||||
const char *path = mod->path;
|
||||
|
||||
if (fs_starts_with(path, "boot():")) path += 7;
|
||||
else if (fs_starts_with(path, "boot:///")) path += 8;
|
||||
|
||||
int path_len = 0;
|
||||
while (path[path_len]) path_len++;
|
||||
|
||||
serial_write("[INIT] Module: ");
|
||||
serial_write(path);
|
||||
serial_write(" (");
|
||||
serial_write_hex(mod->size);
|
||||
serial_write(" bytes)\n");
|
||||
|
||||
if (path_len >= 5 && path[path_len-4] == '.' && path[path_len-3] == 't' &&
|
||||
path[path_len-2] == 'a' && path[path_len-1] == 'r') {
|
||||
g_bootfs_state.initrd_size = mod->size;
|
||||
serial_write("[INIT] -> Initrd detected\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vfs_mount("/boot", "bootfs", "bootfs", bootfs_get_ops(), NULL);
|
||||
|
||||
if (module_request.response == NULL) {
|
||||
serial_write("[DEBUG] ERROR: Limine Module Response is NULL!\n");
|
||||
log_fail("Limine module response NULL");
|
||||
} else {
|
||||
serial_write("[DEBUG] Limine Module Response found. Count: ");
|
||||
serial_write_num(module_request.response->module_count);
|
||||
serial_write("\n");
|
||||
log_ok("Limine modules loaded");
|
||||
for (uint64_t i = 0; i < module_request.response->module_count; i++) {
|
||||
struct limine_file *mod = module_request.response->modules[i];
|
||||
|
||||
@@ -215,7 +350,7 @@ void kmain(void) {
|
||||
while(clean_path[len]) len++;
|
||||
|
||||
if (len >= 4 && clean_path[len-4] == '.' && clean_path[len-3] == 't' && clean_path[len-2] == 'a' && clean_path[len-1] == 'r') {
|
||||
serial_write("[DEBUG] Parsing TAR initrd: ");
|
||||
serial_write("[INIT] Parsing TAR initrd: ");
|
||||
serial_write(clean_path);
|
||||
serial_write("\n");
|
||||
tar_parse(mod->address, mod->size);
|
||||
@@ -237,13 +372,13 @@ void kmain(void) {
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
module_manager_register(clean_path, (uint64_t)mod->address, mod->size);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize fonts now that FAT32 and modules are loaded
|
||||
uint64_t current_rsp;
|
||||
asm volatile("mov %%rsp, %0" : "=r"(current_rsp));
|
||||
serial_write("[DEBUG] Stack Alignment: 0x");
|
||||
serial_write("[INIT] Stack Alignment: 0x");
|
||||
serial_write_hex(current_rsp);
|
||||
serial_write("\n");
|
||||
|
||||
@@ -253,18 +388,13 @@ void kmain(void) {
|
||||
ps2_init();
|
||||
asm("sti");
|
||||
|
||||
// Initialize LAPIC for IPI support
|
||||
lapic_init();
|
||||
|
||||
// Initialize SMP — bring up all CPU cores
|
||||
if (smp_request.response != NULL) {
|
||||
uint32_t online = smp_init(smp_request.response);
|
||||
serial_write("[DEBUG] SMP init complete, CPUs online: ");
|
||||
serial_write_num(online);
|
||||
serial_write("\n");
|
||||
log_ok("SMP initialized");
|
||||
} else {
|
||||
serial_write("[DEBUG] No SMP response from bootloader\n");
|
||||
// Still init as single-CPU
|
||||
serial_write("[INIT] No SMP response from bootloader\n");
|
||||
smp_init(NULL);
|
||||
}
|
||||
|
||||
@@ -272,6 +402,9 @@ void kmain(void) {
|
||||
|
||||
asm volatile("sti");
|
||||
|
||||
extern void bootfs_refresh_from_disk(void);
|
||||
bootfs_refresh_from_disk();
|
||||
|
||||
while (1) {
|
||||
wm_process_input();
|
||||
wm_process_deferred_thumbs();
|
||||
|
||||
@@ -14,8 +14,8 @@ static size_t man_strlen(const char *str) {
|
||||
}
|
||||
|
||||
static void write_man_file(const char *name, const char *content) {
|
||||
char path[128] = "A:/Library/man/";
|
||||
int i = 15;
|
||||
char path[128] = "/Library/man/";
|
||||
int i = 13;
|
||||
while (*name) path[i++] = *name++;
|
||||
path[i++] = '.';
|
||||
path[i++] = 't';
|
||||
@@ -31,8 +31,8 @@ static void write_man_file(const char *name, const char *content) {
|
||||
}
|
||||
|
||||
void create_man_entries(void) {
|
||||
fat32_mkdir("A:/Library");
|
||||
fat32_mkdir("A:/Library/man");
|
||||
fat32_mkdir("/Library");
|
||||
fat32_mkdir("/Library/man");
|
||||
|
||||
write_man_file("ping", "PING - Send ICMP echo requests\n\nUsage: ping <ip>\n\nSends ICMP echo requests to the specified IP address and displays the response times.");
|
||||
write_man_file("net", "NET - Network utilities\n\nUsage: net init\nnet info\nnet ipset >ip<\nnet udpsend >ip< >port< >message< net ping >ip< net help\n\nA collection of network-related commands.");
|
||||
@@ -55,7 +55,8 @@ void create_man_entries(void) {
|
||||
write_man_file("touch", "TOUCH - Create empty file\n\nUsage: touch <filename>\n\nCreates a new empty file if it doesn't exist.");
|
||||
write_man_file("cc", "CC - C Compiler\n\nUsage: cc <file.c>\n\nThe BoredOS C Compiler. Compiles C source files into executables. (execute these with ./>file<)");
|
||||
write_man_file("crash", "CRASH - Trigger kernel exception\n\nUsage: crash\n\nIntentionally triggers a null pointer dereference to test handlers.");
|
||||
write_man_file("sysfetch", "SYSFETCH - Show OS information\n\nUsage: sysfetch\n\nDisplays system information in a neofetch-like layout. Configurable via A:/Library/conf/sysfetch.cfg.");
|
||||
write_man_file("sysfetch", "SYSFETCH - Show OS information\n\nUsage: sysfetch\n\nDisplays system information in a neofetch-like layout. Configurable via /Library/conf/sysfetch.cfg.");
|
||||
write_man_file("uname", "UNAME - Print system information\n\nUsage: uname [-amnoprsv]\n\nOptions:\n -a Print all information\n -s Kernel name\n -n Node name\n -r Kernel release\n -v Kernel build date and time\n -m Machine hardware name\n -p Processor type\n -o Operating system name");
|
||||
write_man_file("meminfo", "MEMINFO - Memory usage stats\n\nUsage: meminfo\n\nDisplays current physical and virtual memory allocation statistics.");
|
||||
write_man_file("pci_list", "PCI_LIST - Scan PCI bus\n\nUsage: pci_list\n\nScans the PCI bus and lists all detected hardware devices.");
|
||||
write_man_file("reboot", "REBOOT - Restart system\n\nUsage: reboot\n\nRestarts the computer immediately.");
|
||||
@@ -69,6 +70,7 @@ void create_man_entries(void) {
|
||||
write_man_file("math", "MATH - Expression evaluator\n\nUsage: math <expression>\n\nEvaluates simple arithmetic expressions from the command line.");
|
||||
write_man_file("viewer", "VIEWER - Image viewer\n\nUsage: viewer <file.ppm>\n\nA graphical application for viewing image files.");
|
||||
write_man_file("settings", "SETTINGS - System settings\n\nUsage: settings\n\nOpens the graphical system configuration tool.");
|
||||
write_man_file("2048", "2048 - Classic game\n\nUsage: 2048\n\nPlays the classic 2048 game.");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <stdint.h>
|
||||
#include "limine.h"
|
||||
#include <stddef.h>
|
||||
#include "platform.h"
|
||||
#include "kutils.h"
|
||||
static volatile struct limine_hhdm_request hhdm_request __attribute__((used, section(".requests"))) = {
|
||||
.id = LIMINE_HHDM_REQUEST,
|
||||
.revision = 0,
|
||||
@@ -69,3 +71,83 @@ void platform_get_cpu_model(char *model) {
|
||||
}
|
||||
model[48] = '\0';
|
||||
}
|
||||
void platform_get_cpu_vendor(char *vendor) {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0));
|
||||
|
||||
char *p = (char *)vendor;
|
||||
*((uint32_t *)&p[0]) = ebx;
|
||||
*((uint32_t *)&p[4]) = edx;
|
||||
*((uint32_t *)&p[8]) = ecx;
|
||||
p[12] = '\0';
|
||||
}
|
||||
|
||||
void platform_get_cpu_info(cpu_info_t *info) {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
|
||||
// CPUID leaf 1: basic feature information
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||
|
||||
info->stepping = eax & 0xF;
|
||||
info->model = (eax >> 4) & 0xF;
|
||||
info->family = (eax >> 8) & 0xF;
|
||||
info->microcode = (ebx >> 8) & 0xFF;
|
||||
info->flags = ((uint64_t)ecx << 32) | edx; // ECX and EDX contain feature flags
|
||||
info->cache_size = (ebx >> 16) & 0xFF; // Cache line size in bytes
|
||||
}
|
||||
|
||||
void platform_get_cpu_flags(char *flags_str) {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
|
||||
flags_str[0] = '\0';
|
||||
|
||||
// CPUID leaf 1
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||
|
||||
// ECX flags
|
||||
if (ecx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "sse3 ");
|
||||
if (ecx & (1 << 1)) k_strcpy(flags_str + k_strlen(flags_str), "pclmulqdq ");
|
||||
if (ecx & (1 << 3)) k_strcpy(flags_str + k_strlen(flags_str), "monitor ");
|
||||
if (ecx & (1 << 6)) k_strcpy(flags_str + k_strlen(flags_str), "ssse3 ");
|
||||
if (ecx & (1 << 9)) k_strcpy(flags_str + k_strlen(flags_str), "sdbg ");
|
||||
if (ecx & (1 << 12)) k_strcpy(flags_str + k_strlen(flags_str), "fma ");
|
||||
if (ecx & (1 << 13)) k_strcpy(flags_str + k_strlen(flags_str), "cx16 ");
|
||||
if (ecx & (1 << 19)) k_strcpy(flags_str + k_strlen(flags_str), "sse4_1 ");
|
||||
if (ecx & (1 << 20)) k_strcpy(flags_str + k_strlen(flags_str), "sse4_2 ");
|
||||
if (ecx & (1 << 23)) k_strcpy(flags_str + k_strlen(flags_str), "popcnt ");
|
||||
if (ecx & (1 << 25)) k_strcpy(flags_str + k_strlen(flags_str), "aes ");
|
||||
if (ecx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "xsave ");
|
||||
if (ecx & (1 << 28)) k_strcpy(flags_str + k_strlen(flags_str), "avx ");
|
||||
|
||||
// EDX flags
|
||||
if (edx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "fpu ");
|
||||
if (edx & (1 << 3)) k_strcpy(flags_str + k_strlen(flags_str), "pse ");
|
||||
if (edx & (1 << 4)) k_strcpy(flags_str + k_strlen(flags_str), "tsc ");
|
||||
if (edx & (1 << 6)) k_strcpy(flags_str + k_strlen(flags_str), "pae ");
|
||||
if (edx & (1 << 8)) k_strcpy(flags_str + k_strlen(flags_str), "cx8 ");
|
||||
if (edx & (1 << 9)) k_strcpy(flags_str + k_strlen(flags_str), "apic ");
|
||||
if (edx & (1 << 11)) k_strcpy(flags_str + k_strlen(flags_str), "sep ");
|
||||
if (edx & (1 << 15)) k_strcpy(flags_str + k_strlen(flags_str), "cmov ");
|
||||
if (edx & (1 << 23)) k_strcpy(flags_str + k_strlen(flags_str), "mmx ");
|
||||
if (edx & (1 << 24)) k_strcpy(flags_str + k_strlen(flags_str), "fxsr ");
|
||||
if (edx & (1 << 25)) k_strcpy(flags_str + k_strlen(flags_str), "sse ");
|
||||
if (edx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "sse2 ");
|
||||
|
||||
// Extended leaf 0x80000001 for advanced flags
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0x80000001));
|
||||
|
||||
if (edx & (1 << 11)) k_strcpy(flags_str + k_strlen(flags_str), "syscall ");
|
||||
if (edx & (1 << 20)) k_strcpy(flags_str + k_strlen(flags_str), "nx ");
|
||||
if (edx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "pdpe1gb ");
|
||||
if (edx & (1 << 27)) k_strcpy(flags_str + k_strlen(flags_str), "rdtscp ");
|
||||
if (edx & (1 << 29)) k_strcpy(flags_str + k_strlen(flags_str), "lm ");
|
||||
|
||||
if (ecx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "lahf_lm ");
|
||||
if (ecx & (1 << 5)) k_strcpy(flags_str + k_strlen(flags_str), "abm ");
|
||||
|
||||
// Remove trailing space
|
||||
int len = k_strlen(flags_str);
|
||||
if (len > 0 && flags_str[len-1] == ' ') {
|
||||
flags_str[len-1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,21 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t family;
|
||||
uint32_t model;
|
||||
uint32_t stepping;
|
||||
uint32_t microcode;
|
||||
uint64_t flags;
|
||||
uint32_t cache_size;
|
||||
} cpu_info_t;
|
||||
|
||||
void platform_init(void);
|
||||
uint64_t p2v(uint64_t phys);
|
||||
uint64_t v2p(uint64_t virt);
|
||||
void platform_get_cpu_model(char *model);
|
||||
void platform_get_cpu_vendor(char *vendor);
|
||||
void platform_get_cpu_info(cpu_info_t *info);
|
||||
void platform_get_cpu_flags(char *flags_str);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,10 +13,10 @@ void get_os_info(os_info_t *info) {
|
||||
for (size_t i = 0; i < sizeof(os_info_t); i++) p[i] = 0;
|
||||
|
||||
const char *os_name = "BoredOS";
|
||||
const char *os_version = "26.4";
|
||||
const char *os_codename = "Geometry";
|
||||
const char *os_version = "26.4.2";
|
||||
const char *os_codename = "Voyager";
|
||||
const char *kernel_name = "Boredkernel";
|
||||
const char *kernel_version = "3.2.3";
|
||||
const char *kernel_version = "4.1.0-stable";
|
||||
const char *build_date = __DATE__;
|
||||
const char *build_time = __TIME__;
|
||||
const char *build_arch = "x86_64";
|
||||
|
||||
516
src/dev/ahci.c
Normal file
516
src/dev/ahci.c
Normal file
@@ -0,0 +1,516 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "ahci.h"
|
||||
#include "pci.h"
|
||||
#include "disk.h"
|
||||
#include "memory_manager.h"
|
||||
#include "paging.h"
|
||||
#include "io.h"
|
||||
#include <stddef.h>
|
||||
#include "../sys/spinlock.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint64_t num);
|
||||
extern void serial_write_hex(uint32_t val);
|
||||
|
||||
// ============================================================================
|
||||
// AHCI Driver State
|
||||
// ============================================================================
|
||||
|
||||
static HBA_MEM *abar = NULL; // MMIO-mapped AHCI Base Address
|
||||
static bool ahci_initialized = false;
|
||||
static int active_port_count = 0;
|
||||
|
||||
#define MAX_AHCI_PORTS 32
|
||||
|
||||
typedef struct {
|
||||
bool active;
|
||||
int port_num;
|
||||
HBA_PORT *port;
|
||||
HBA_CMD_HEADER *cmd_list; // 1KB, 1KB aligned
|
||||
void *fis_base; // 256B, 256B aligned
|
||||
HBA_CMD_TBL *cmd_tbl; // Command table for slot 0
|
||||
spinlock_t lock; // Port-level lock for thread-safety
|
||||
} ahci_port_state_t;
|
||||
|
||||
static ahci_port_state_t ports[MAX_AHCI_PORTS];
|
||||
|
||||
// ============================================================================
|
||||
// String Helpers
|
||||
// ============================================================================
|
||||
|
||||
static void ahci_strcpy(char *d, const char *s) {
|
||||
while ((*d++ = *s++));
|
||||
}
|
||||
|
||||
// Kernel virtual to physical address conversion
|
||||
extern uint64_t v2p(uint64_t vaddr);
|
||||
|
||||
// ============================================================================
|
||||
// Port Setup
|
||||
// ============================================================================
|
||||
|
||||
static void ahci_stop_cmd(HBA_PORT *port) {
|
||||
// Clear ST (Start)
|
||||
port->cmd &= ~HBA_PORT_CMD_ST;
|
||||
|
||||
// Clear FRE (FIS Receive Enable)
|
||||
port->cmd &= ~HBA_PORT_CMD_FRE;
|
||||
|
||||
// Wait until FR and CR clear
|
||||
int timeout = 500000;
|
||||
while (timeout-- > 0) {
|
||||
if (port->cmd & HBA_PORT_CMD_FR) continue;
|
||||
if (port->cmd & HBA_PORT_CMD_CR) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ahci_start_cmd(HBA_PORT *port) {
|
||||
// Wait until CR clears
|
||||
while (port->cmd & HBA_PORT_CMD_CR);
|
||||
|
||||
// Set FRE and ST
|
||||
port->cmd |= HBA_PORT_CMD_FRE;
|
||||
port->cmd |= HBA_PORT_CMD_ST;
|
||||
}
|
||||
|
||||
static int ahci_check_port_type(HBA_PORT *port) {
|
||||
uint32_t ssts = port->ssts;
|
||||
uint8_t ipm = (ssts >> 8) & 0x0F;
|
||||
uint8_t det = ssts & 0x0F;
|
||||
|
||||
if (det != 3) return -1; // No device detected
|
||||
if (ipm != 1) return -1; // Not in active state
|
||||
|
||||
switch (port->sig) {
|
||||
case SATA_SIG_ATA: return 0; // SATA drive
|
||||
case SATA_SIG_ATAPI: return 1; // SATAPI drive
|
||||
case SATA_SIG_SEMB: return 2; // SEMB
|
||||
case SATA_SIG_PM: return 3; // Port multiplier
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void ahci_port_rebase(ahci_port_state_t *ps) {
|
||||
HBA_PORT *port = ps->port;
|
||||
|
||||
ahci_stop_cmd(port);
|
||||
|
||||
// Allocate command list (1KB, 1024-byte aligned)
|
||||
ps->cmd_list = (HBA_CMD_HEADER*)kmalloc_aligned(1024, 1024);
|
||||
if (!ps->cmd_list) return;
|
||||
mem_memset(ps->cmd_list, 0, 1024);
|
||||
|
||||
uint64_t clb_phys = v2p((uint64_t)ps->cmd_list);
|
||||
port->clb = (uint32_t)(clb_phys & 0xFFFFFFFF);
|
||||
port->clbu = (uint32_t)(clb_phys >> 32);
|
||||
|
||||
// Allocate FIS receive area (256 bytes, 256-byte aligned)
|
||||
ps->fis_base = kmalloc_aligned(256, 256);
|
||||
if (!ps->fis_base) return;
|
||||
mem_memset(ps->fis_base, 0, 256);
|
||||
|
||||
uint64_t fb_phys = v2p((uint64_t)ps->fis_base);
|
||||
port->fb = (uint32_t)(fb_phys & 0xFFFFFFFF);
|
||||
port->fbu = (uint32_t)(fb_phys >> 32);
|
||||
|
||||
// Allocate command table for slot 0 (256-byte aligned, room for 8 PRDT entries)
|
||||
int cmd_tbl_size = sizeof(HBA_CMD_TBL) + 8 * sizeof(HBA_PRDT_ENTRY);
|
||||
ps->cmd_tbl = (HBA_CMD_TBL*)kmalloc_aligned(cmd_tbl_size, 256);
|
||||
if (!ps->cmd_tbl) return;
|
||||
mem_memset(ps->cmd_tbl, 0, cmd_tbl_size);
|
||||
|
||||
// Set command header 0 to point to our command table
|
||||
uint64_t ctba_phys = v2p((uint64_t)ps->cmd_tbl);
|
||||
ps->cmd_list[0].ctba = (uint32_t)(ctba_phys & 0xFFFFFFFF);
|
||||
ps->cmd_list[0].ctbau = (uint32_t)(ctba_phys >> 32);
|
||||
ps->cmd_list[0].prdtl = 1; // 1 PRDT entry default
|
||||
|
||||
// Clear error and interrupt status
|
||||
port->serr = 0xFFFFFFFF;
|
||||
port->is = 0xFFFFFFFF;
|
||||
|
||||
ahci_start_cmd(port);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sector I/O
|
||||
// ============================================================================
|
||||
|
||||
static int ahci_find_free_slot(HBA_PORT *port) {
|
||||
uint32_t slots = (port->sact | port->ci);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (!(slots & (1 << i))) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ahci_read_sectors(int port_num, uint64_t lba, uint32_t count, uint8_t *buffer) {
|
||||
if (!ahci_initialized || port_num < 0 || port_num >= MAX_AHCI_PORTS) return -1;
|
||||
ahci_port_state_t *ps = &ports[port_num];
|
||||
if (!ps->active) return -1;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&ps->lock);
|
||||
HBA_PORT *port = ps->port;
|
||||
|
||||
// Clear any pending interrupts/errors
|
||||
port->is = 0xFFFFFFFF;
|
||||
|
||||
int slot = ahci_find_free_slot(port);
|
||||
if (slot < 0) return -1;
|
||||
|
||||
HBA_CMD_HEADER *cmd_hdr = &ps->cmd_list[slot];
|
||||
cmd_hdr->cfl = sizeof(FIS_REG_H2D) / sizeof(uint32_t);
|
||||
cmd_hdr->w = 0; // Read
|
||||
cmd_hdr->prdtl = 1;
|
||||
|
||||
HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl;
|
||||
mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + sizeof(HBA_PRDT_ENTRY));
|
||||
|
||||
// Setup PRDT
|
||||
uint64_t buf_phys = v2p((uint64_t)buffer);
|
||||
cmd_tbl->prdt[0].dba = (uint32_t)(buf_phys & 0xFFFFFFFF);
|
||||
cmd_tbl->prdt[0].dbau = (uint32_t)(buf_phys >> 32);
|
||||
cmd_tbl->prdt[0].dbc = (count * 512) - 1; // 0-based byte count
|
||||
cmd_tbl->prdt[0].i = 1;
|
||||
|
||||
// Setup Command FIS
|
||||
FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis;
|
||||
fis->fis_type = FIS_TYPE_REG_H2D;
|
||||
fis->c = 1; // Command
|
||||
fis->command = ATA_CMD_READ_DMA_EX;
|
||||
|
||||
fis->lba0 = (uint8_t)(lba);
|
||||
fis->lba1 = (uint8_t)(lba >> 8);
|
||||
fis->lba2 = (uint8_t)(lba >> 16);
|
||||
fis->device = 1 << 6; // LBA mode
|
||||
fis->lba3 = (uint8_t)(lba >> 24);
|
||||
fis->lba4 = (uint8_t)(lba >> 32);
|
||||
fis->lba5 = (uint8_t)(lba >> 40);
|
||||
|
||||
fis->countl = (uint8_t)(count);
|
||||
fis->counth = (uint8_t)(count >> 8);
|
||||
|
||||
// Issue command
|
||||
port->ci = (1 << slot);
|
||||
|
||||
// Wait for completion
|
||||
int timeout = 1000000;
|
||||
while (timeout-- > 0) {
|
||||
if (!(port->ci & (1 << slot))) break;
|
||||
if (port->is & (1 << 30)) { // Task File Error
|
||||
serial_write("\n");
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout <= 0) {
|
||||
serial_write("[AHCI] Read timeout on port ");
|
||||
serial_write_num(port_num);
|
||||
serial_write("\n");
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ahci_write_sectors(int port_num, uint64_t lba, uint32_t count, const uint8_t *buffer) {
|
||||
if (!ahci_initialized || port_num < 0 || port_num >= MAX_AHCI_PORTS) return -1;
|
||||
ahci_port_state_t *ps = &ports[port_num];
|
||||
if (!ps->active) return -1;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&ps->lock);
|
||||
HBA_PORT *port = ps->port;
|
||||
|
||||
port->is = 0xFFFFFFFF;
|
||||
|
||||
int slot = ahci_find_free_slot(port);
|
||||
if (slot < 0) return -1;
|
||||
|
||||
HBA_CMD_HEADER *cmd_hdr = &ps->cmd_list[slot];
|
||||
cmd_hdr->cfl = sizeof(FIS_REG_H2D) / sizeof(uint32_t);
|
||||
cmd_hdr->w = 1; // Write
|
||||
cmd_hdr->prdtl = 1;
|
||||
|
||||
HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl;
|
||||
mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + sizeof(HBA_PRDT_ENTRY));
|
||||
|
||||
uint64_t buf_phys = v2p((uint64_t)buffer);
|
||||
cmd_tbl->prdt[0].dba = (uint32_t)(buf_phys & 0xFFFFFFFF);
|
||||
cmd_tbl->prdt[0].dbau = (uint32_t)(buf_phys >> 32);
|
||||
cmd_tbl->prdt[0].dbc = (count * 512) - 1;
|
||||
cmd_tbl->prdt[0].i = 1;
|
||||
|
||||
FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis;
|
||||
fis->fis_type = FIS_TYPE_REG_H2D;
|
||||
fis->c = 1;
|
||||
fis->command = ATA_CMD_WRITE_DMA_EX;
|
||||
|
||||
fis->lba0 = (uint8_t)(lba);
|
||||
fis->lba1 = (uint8_t)(lba >> 8);
|
||||
fis->lba2 = (uint8_t)(lba >> 16);
|
||||
fis->device = 1 << 6;
|
||||
fis->lba3 = (uint8_t)(lba >> 24);
|
||||
fis->lba4 = (uint8_t)(lba >> 32);
|
||||
fis->lba5 = (uint8_t)(lba >> 40);
|
||||
|
||||
fis->countl = (uint8_t)(count);
|
||||
fis->counth = (uint8_t)(count >> 8);
|
||||
|
||||
port->ci = (1 << slot);
|
||||
|
||||
int timeout = 1000000;
|
||||
while (timeout-- > 0) {
|
||||
if (!(port->ci & (1 << slot))) break;
|
||||
if (port->is & (1 << 30)) {
|
||||
serial_write("[AHCI] Write error on port ");
|
||||
serial_write_num(port_num);
|
||||
serial_write("\n");
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout <= 0) {
|
||||
serial_write("[AHCI] Write timeout on port ");
|
||||
serial_write_num(port_num);
|
||||
serial_write("\n");
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&ps->lock, rflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// AHCI Disk Integration — wrap AHCI into Disk read/write_sector
|
||||
// ============================================================================
|
||||
|
||||
typedef struct {
|
||||
int ahci_port;
|
||||
} AHCIDriverData;
|
||||
|
||||
static int ahci_disk_read_sector(Disk *disk, uint32_t sector, uint8_t *buffer) {
|
||||
AHCIDriverData *data = (AHCIDriverData*)disk->driver_data;
|
||||
|
||||
// For partitions, add offset and use parent's port
|
||||
if (disk->is_partition && disk->parent) {
|
||||
AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data;
|
||||
return ahci_read_sectors(pdata->ahci_port,
|
||||
(uint64_t)sector + disk->partition_lba_offset, 1, buffer);
|
||||
}
|
||||
|
||||
return ahci_read_sectors(data->ahci_port, (uint64_t)sector, 1, buffer);
|
||||
}
|
||||
|
||||
static int ahci_disk_write_sector(Disk *disk, uint32_t sector, const uint8_t *buffer) {
|
||||
AHCIDriverData *data = (AHCIDriverData*)disk->driver_data;
|
||||
|
||||
if (disk->is_partition && disk->parent) {
|
||||
AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data;
|
||||
return ahci_write_sectors(pdata->ahci_port,
|
||||
(uint64_t)sector + disk->partition_lba_offset, 1, buffer);
|
||||
}
|
||||
|
||||
return ahci_write_sectors(data->ahci_port, (uint64_t)sector, 1, buffer);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Initialization
|
||||
// ============================================================================
|
||||
|
||||
int ahci_get_port_count(void) {
|
||||
return active_port_count;
|
||||
}
|
||||
|
||||
bool ahci_port_is_active(int port_num) {
|
||||
if (port_num < 0 || port_num >= MAX_AHCI_PORTS) return false;
|
||||
return ports[port_num].active;
|
||||
}
|
||||
|
||||
void ahci_init(void) {
|
||||
serial_write("[AHCI] Scanning PCI for AHCI controller...\n");
|
||||
|
||||
// Find AHCI controller (Class 0x01, Subclass 0x06)
|
||||
pci_device_t pci_dev;
|
||||
if (!pci_find_device_by_class(PCI_CLASS_MASS_STORAGE, PCI_SUBCLASS_SATA, &pci_dev)) {
|
||||
serial_write("[AHCI] No AHCI controller found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
serial_write("[AHCI] Found AHCI controller (");
|
||||
serial_write("vendor=0x");
|
||||
serial_write_hex(pci_dev.vendor_id);
|
||||
serial_write(", device=0x");
|
||||
serial_write_hex(pci_dev.device_id);
|
||||
serial_write(")\n");
|
||||
|
||||
// Enable Bus Mastering and MMIO
|
||||
pci_enable_bus_mastering(&pci_dev);
|
||||
pci_enable_mmio(&pci_dev);
|
||||
|
||||
// Read ABAR (BAR5)
|
||||
uint32_t abar_raw = pci_get_bar(&pci_dev, 5);
|
||||
uint64_t abar_phys = abar_raw & 0xFFFFF000; // Mask out lower bits
|
||||
|
||||
if (abar_phys == 0) {
|
||||
serial_write("[AHCI] Invalid ABAR address\n");
|
||||
return;
|
||||
}
|
||||
|
||||
serial_write("[AHCI] ABAR physical address: 0x");
|
||||
serial_write_hex((uint32_t)abar_phys);
|
||||
serial_write("\n");
|
||||
|
||||
// Map ABAR region into kernel virtual address space
|
||||
// Identity-map several pages to cover the HBA memory (at least 0x1100 bytes)
|
||||
uint64_t abar_virt = abar_phys; // Use identity mapping
|
||||
for (uint64_t offset = 0; offset < 0x2000; offset += 4096) {
|
||||
paging_map_page(paging_get_pml4_phys(), abar_virt + offset,
|
||||
abar_phys + offset,
|
||||
PT_PRESENT | PT_RW | PT_CACHE_DISABLE);
|
||||
}
|
||||
|
||||
abar = (HBA_MEM*)abar_virt;
|
||||
|
||||
// Enable AHCI mode
|
||||
abar->ghc |= (1 << 31); // AE (AHCI Enable)
|
||||
|
||||
serial_write("[AHCI] Version: ");
|
||||
serial_write_num(abar->vs >> 16);
|
||||
serial_write(".");
|
||||
serial_write_num(abar->vs & 0xFFFF);
|
||||
serial_write("\n");
|
||||
|
||||
// Probe ports
|
||||
uint32_t pi = abar->pi;
|
||||
active_port_count = 0;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
ports[i].active = false;
|
||||
|
||||
HBA_PORT *port = &abar->ports[i];
|
||||
ports[i].lock = SPINLOCK_INIT;
|
||||
int type = ahci_check_port_type(port);
|
||||
|
||||
if (type == 0) { // SATA drive
|
||||
serial_write("[AHCI] Port ");
|
||||
serial_write_num(i);
|
||||
serial_write(": SATA drive detected\n");
|
||||
|
||||
ports[i].port_num = i;
|
||||
ports[i].port = port;
|
||||
ahci_port_rebase(&ports[i]);
|
||||
ports[i].active = true;
|
||||
active_port_count++;
|
||||
|
||||
// Register as a block device
|
||||
Disk *disk = (Disk*)kmalloc(sizeof(Disk));
|
||||
if (disk) {
|
||||
AHCIDriverData *drv = (AHCIDriverData*)kmalloc(sizeof(AHCIDriverData));
|
||||
drv->ahci_port = i;
|
||||
|
||||
disk->devname[0] = 0; // Auto-assign
|
||||
disk->type = DISK_TYPE_SATA;
|
||||
ahci_strcpy(disk->label, "SATA Drive");
|
||||
disk->read_sector = ahci_disk_read_sector;
|
||||
disk->write_sector = ahci_disk_write_sector;
|
||||
disk->driver_data = drv;
|
||||
disk->partition_lba_offset = 0;
|
||||
disk->total_sectors = 0;
|
||||
disk->parent = NULL;
|
||||
disk->is_partition = false;
|
||||
disk->is_fat32 = false;
|
||||
|
||||
disk_register(disk);
|
||||
|
||||
// Let disk_manager parse partitions — we call a scan function
|
||||
extern void disk_manager_scan_partitions(Disk *disk);
|
||||
// Inline MBR parse for this disk
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("[AHCI] Probing partitions on /dev/");
|
||||
serial_write(disk->devname);
|
||||
serial_write("...\n");
|
||||
|
||||
// Read MBR sector 0
|
||||
uint8_t *mbr_buf = (uint8_t*)kmalloc(512);
|
||||
if (mbr_buf) {
|
||||
if (ahci_disk_read_sector(disk, 0, mbr_buf) == 0) {
|
||||
if (mbr_buf[510] == 0x55 && mbr_buf[511] == 0xAA) {
|
||||
// Parse MBR partition table
|
||||
typedef struct {
|
||||
uint8_t status;
|
||||
uint8_t chs_first[3];
|
||||
uint8_t type;
|
||||
uint8_t chs_last[3];
|
||||
uint32_t lba_start;
|
||||
uint32_t sector_count;
|
||||
} __attribute__((packed)) MBR_Part;
|
||||
|
||||
MBR_Part *parts = (MBR_Part*)&mbr_buf[446];
|
||||
int pn = 1;
|
||||
for (int p = 0; p < 4; p++) {
|
||||
if (parts[p].type == 0x00 || parts[p].sector_count == 0)
|
||||
continue;
|
||||
|
||||
bool fat32 = false;
|
||||
if (parts[p].type == 0x0B || parts[p].type == 0x0C) {
|
||||
// Verify BPB
|
||||
uint8_t *pbuf = (uint8_t*)kmalloc(512);
|
||||
if (pbuf) {
|
||||
if (ahci_disk_read_sector(disk, parts[p].lba_start, pbuf) == 0) {
|
||||
if (pbuf[510] == 0x55 && pbuf[511] == 0xAA) {
|
||||
uint16_t bps = *(uint16_t*)&pbuf[11];
|
||||
uint16_t spf16 = *(uint16_t*)&pbuf[22];
|
||||
uint32_t spf32 = *(uint32_t*)&pbuf[36];
|
||||
if (bps == 512 && spf16 == 0 && spf32 > 0)
|
||||
fat32 = true;
|
||||
}
|
||||
}
|
||||
kfree(pbuf);
|
||||
}
|
||||
}
|
||||
|
||||
disk_register_partition(disk, parts[p].lba_start,
|
||||
parts[p].sector_count, fat32, pn);
|
||||
pn++;
|
||||
}
|
||||
|
||||
// Fallback: raw FAT32
|
||||
if (pn == 1) {
|
||||
uint16_t bps = *(uint16_t*)&mbr_buf[11];
|
||||
uint16_t spf16 = *(uint16_t*)&mbr_buf[22];
|
||||
uint32_t spf32 = *(uint32_t*)&mbr_buf[36];
|
||||
if (bps == 512 && spf16 == 0 && spf32 > 0) {
|
||||
disk->is_fat32 = true;
|
||||
disk->partition_lba_offset = 0;
|
||||
serial_write("[AHCI] Raw FAT32 volume detected\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree(mbr_buf);
|
||||
}
|
||||
}
|
||||
} else if (type == 1) {
|
||||
serial_write("[AHCI] Port ");
|
||||
serial_write_num(i);
|
||||
serial_write(": SATAPI drive (ignored)\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (active_port_count > 0) {
|
||||
ahci_initialized = true;
|
||||
serial_write("[AHCI] Initialization complete: ");
|
||||
serial_write_num(active_port_count);
|
||||
serial_write(" SATA port(s) active\n");
|
||||
} else {
|
||||
serial_write("[AHCI] No active SATA ports found\n");
|
||||
}
|
||||
}
|
||||
174
src/dev/ahci.h
Normal file
174
src/dev/ahci.h
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 AHCI_H
|
||||
#define AHCI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ============================================================================
|
||||
// FIS (Frame Information Structure) Types
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
FIS_TYPE_REG_H2D = 0x27, // Register FIS — Host to Device
|
||||
FIS_TYPE_REG_D2H = 0x34, // Register FIS — Device to Host
|
||||
FIS_TYPE_DMA_ACT = 0x39, // DMA Activate FIS
|
||||
FIS_TYPE_DMA_SETUP = 0x41, // DMA Setup FIS
|
||||
FIS_TYPE_DATA = 0x46, // Data FIS
|
||||
FIS_TYPE_BIST = 0x58, // BIST Activate FIS
|
||||
FIS_TYPE_PIO_SETUP = 0x5F, // PIO Setup FIS
|
||||
FIS_TYPE_DEV_BITS = 0xA1, // Set Device Bits FIS
|
||||
} FIS_TYPE;
|
||||
|
||||
// ============================================================================
|
||||
// HBA Register Structures (MMIO-mapped from ABAR)
|
||||
// ============================================================================
|
||||
|
||||
// Port Registers (one set per port, at ABAR + 0x100 + portno*0x80)
|
||||
typedef volatile struct {
|
||||
uint32_t clb; // 0x00: Command List Base Address (lower 32 bits)
|
||||
uint32_t clbu; // 0x04: Command List Base Address (upper 32 bits)
|
||||
uint32_t fb; // 0x08: FIS Base Address (lower 32 bits)
|
||||
uint32_t fbu; // 0x0C: FIS Base Address (upper 32 bits)
|
||||
uint32_t is; // 0x10: Interrupt Status
|
||||
uint32_t ie; // 0x14: Interrupt Enable
|
||||
uint32_t cmd; // 0x18: Command and Status
|
||||
uint32_t rsv0; // 0x1C: Reserved
|
||||
uint32_t tfd; // 0x20: Task File Data
|
||||
uint32_t sig; // 0x24: Signature
|
||||
uint32_t ssts; // 0x28: SATA Status (SStatus)
|
||||
uint32_t sctl; // 0x2C: SATA Control (SControl)
|
||||
uint32_t serr; // 0x30: SATA Error (SError)
|
||||
uint32_t sact; // 0x34: SATA Active (SCR3)
|
||||
uint32_t ci; // 0x38: Command Issue
|
||||
uint32_t sntf; // 0x3C: SATA Notification (SCR4)
|
||||
uint32_t fbs; // 0x40: FIS-based Switch Control
|
||||
uint32_t rsv1[11]; // 0x44~0x6F
|
||||
uint32_t vendor[4]; // 0x70~0x7F
|
||||
} HBA_PORT;
|
||||
|
||||
// Global HBA Memory Registers (at ABAR)
|
||||
typedef volatile struct {
|
||||
uint32_t cap; // 0x00: Host Capability
|
||||
uint32_t ghc; // 0x04: Global Host Control
|
||||
uint32_t is; // 0x08: Interrupt Status
|
||||
uint32_t pi; // 0x0C: Port Implemented
|
||||
uint32_t vs; // 0x10: Version
|
||||
uint32_t ccc_ctl; // 0x14: Command Completion Coalescing Control
|
||||
uint32_t ccc_pts; // 0x18: Command Completion Coalescing Ports
|
||||
uint32_t em_loc; // 0x1C: Enclosure Management Location
|
||||
uint32_t em_ctl; // 0x20: Enclosure Management Control
|
||||
uint32_t cap2; // 0x24: Host Capabilities Extended
|
||||
uint32_t bohc; // 0x28: BIOS/OS Handoff Control and Status
|
||||
uint8_t rsv[0xA0 - 0x2C];
|
||||
uint8_t vendor[0x100 - 0xA0];
|
||||
HBA_PORT ports[]; // Port 0 at offset 0x100 (flexible array member)
|
||||
} HBA_MEM;
|
||||
|
||||
// ============================================================================
|
||||
// Command List / Table Structures (DMA)
|
||||
// ============================================================================
|
||||
|
||||
// Command Header (32 bytes each, 32 entries per port = 1KB)
|
||||
typedef struct {
|
||||
uint8_t cfl:5; // Command FIS Length (in DWORDs)
|
||||
uint8_t a:1; // ATAPI
|
||||
uint8_t w:1; // Write (1=H2D, 0=D2H)
|
||||
uint8_t p:1; // Prefetchable
|
||||
|
||||
uint8_t r:1; // Reset
|
||||
uint8_t b:1; // BIST
|
||||
uint8_t c:1; // Clear Busy upon R_OK
|
||||
uint8_t rsv0:1;
|
||||
uint8_t pmp:4; // Port Multiplier Port
|
||||
|
||||
uint16_t prdtl; // Physical Region Descriptor Table Length (entries)
|
||||
|
||||
volatile uint32_t prdbc; // PRD Byte Count transferred
|
||||
|
||||
uint32_t ctba; // Command Table Descriptor Base Address (lower 32)
|
||||
uint32_t ctbau; // Command Table Descriptor Base Address (upper 32)
|
||||
|
||||
uint32_t rsv1[4]; // Reserved
|
||||
} __attribute__((packed)) HBA_CMD_HEADER;
|
||||
|
||||
// Physical Region Descriptor Table Entry
|
||||
typedef struct {
|
||||
uint32_t dba; // Data Base Address (lower 32)
|
||||
uint32_t dbau; // Data Base Address (upper 32)
|
||||
uint32_t rsv0; // Reserved
|
||||
uint32_t dbc:22; // Byte Count (0-based, max 4MB)
|
||||
uint32_t rsv1:9; // Reserved
|
||||
uint32_t i:1; // Interrupt on Completion
|
||||
} __attribute__((packed)) HBA_PRDT_ENTRY;
|
||||
|
||||
// Host-to-Device Register FIS
|
||||
typedef struct {
|
||||
uint8_t fis_type; // FIS_TYPE_REG_H2D
|
||||
uint8_t pmport:4; // Port Multiplier
|
||||
uint8_t rsv0:3; // Reserved
|
||||
uint8_t c:1; // 1=Command, 0=Control
|
||||
uint8_t command; // Command register
|
||||
uint8_t featurel; // Feature register (7:0)
|
||||
uint8_t lba0; // LBA (7:0)
|
||||
uint8_t lba1; // LBA (15:8)
|
||||
uint8_t lba2; // LBA (23:16)
|
||||
uint8_t device; // Device register
|
||||
uint8_t lba3; // LBA (31:24)
|
||||
uint8_t lba4; // LBA (39:32)
|
||||
uint8_t lba5; // LBA (47:40)
|
||||
uint8_t featureh; // Feature register (15:8)
|
||||
uint8_t countl; // Count (7:0)
|
||||
uint8_t counth; // Count (15:8)
|
||||
uint8_t icc; // Isochronous Command Completion
|
||||
uint8_t control; // Control register
|
||||
uint8_t rsv1[4]; // Reserved
|
||||
} __attribute__((packed)) FIS_REG_H2D;
|
||||
|
||||
// Command Table (256-byte aligned)
|
||||
typedef struct {
|
||||
uint8_t cfis[64]; // Command FIS
|
||||
uint8_t acmd[16]; // ATAPI Command
|
||||
uint8_t rsv[48]; // Reserved
|
||||
HBA_PRDT_ENTRY prdt[]; // PRDT entries (variable, at least 1)
|
||||
} __attribute__((packed)) HBA_CMD_TBL;
|
||||
|
||||
// ============================================================================
|
||||
// Port Signature Values
|
||||
// ============================================================================
|
||||
|
||||
#define SATA_SIG_ATA 0x00000101 // SATA drive
|
||||
#define SATA_SIG_ATAPI 0xEB140101 // SATAPI drive
|
||||
#define SATA_SIG_SEMB 0xC33C0101 // Enclosure management bridge
|
||||
#define SATA_SIG_PM 0x96690101 // Port multiplier
|
||||
|
||||
// ============================================================================
|
||||
// Port Command Bits
|
||||
// ============================================================================
|
||||
|
||||
#define HBA_PORT_CMD_ST 0x0001 // Start
|
||||
#define HBA_PORT_CMD_FRE 0x0010 // FIS Receive Enable
|
||||
#define HBA_PORT_CMD_FR 0x4000 // FIS Receive Running
|
||||
#define HBA_PORT_CMD_CR 0x8000 // Command List Running
|
||||
|
||||
// ============================================================================
|
||||
// ATA Commands
|
||||
// ============================================================================
|
||||
|
||||
#define ATA_CMD_READ_DMA_EX 0x25
|
||||
#define ATA_CMD_WRITE_DMA_EX 0x35
|
||||
#define ATA_CMD_IDENTIFY 0xEC
|
||||
|
||||
// ============================================================================
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
void ahci_init(void);
|
||||
int ahci_read_sectors(int port_num, uint64_t lba, uint32_t count, uint8_t *buffer);
|
||||
int ahci_write_sectors(int port_num, uint64_t lba, uint32_t count, const uint8_t *buffer);
|
||||
int ahci_get_port_count(void);
|
||||
bool ahci_port_is_active(int port_num);
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#define SECTOR_SIZE 512
|
||||
#define MAX_DISKS 16
|
||||
|
||||
typedef enum {
|
||||
DISK_TYPE_RAM,
|
||||
@@ -17,11 +18,12 @@ typedef enum {
|
||||
} DiskType;
|
||||
|
||||
typedef struct Disk {
|
||||
char letter;
|
||||
char devname[16]; // Device name: "sda", "sdb", "sda1", etc.
|
||||
DiskType type;
|
||||
bool is_fat32;
|
||||
char name[32];
|
||||
uint32_t partition_lba_offset; // LBA offset of FAT32 partition (0 for raw)
|
||||
char label[32]; // Human-readable label
|
||||
uint32_t partition_lba_offset; // LBA offset of partition (0 for whole disk)
|
||||
uint32_t total_sectors; // Total sectors on this device/partition
|
||||
|
||||
// Function pointers for driver operations
|
||||
int (*read_sector)(struct Disk *disk, uint32_t sector, uint8_t *buffer);
|
||||
@@ -29,14 +31,32 @@ typedef struct Disk {
|
||||
|
||||
// Private driver data
|
||||
void *driver_data;
|
||||
|
||||
// Parent disk (for partitions — points to the whole-disk Disk)
|
||||
struct Disk *parent;
|
||||
bool is_partition;
|
||||
bool registered;
|
||||
} Disk;
|
||||
|
||||
// Initialization and scanning
|
||||
void disk_manager_init(void);
|
||||
void disk_manager_scan(void); // Scans for new disks
|
||||
Disk* disk_get_by_letter(char letter);
|
||||
char disk_get_next_free_letter(void);
|
||||
void disk_manager_scan(void);
|
||||
|
||||
// Device registration
|
||||
void disk_register(Disk *disk);
|
||||
void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count,
|
||||
bool is_fat32, int part_num);
|
||||
|
||||
// Lookup
|
||||
Disk* disk_get_by_name(const char *devname);
|
||||
int disk_get_count(void);
|
||||
Disk* disk_get_by_index(int index);
|
||||
|
||||
// Auto-naming helpers
|
||||
const char* disk_get_next_dev_name(void); // Returns "sda", "sdb", etc.
|
||||
|
||||
// Backward compat (deprecated — wraps disk_get_by_name)
|
||||
Disk* disk_get_by_letter(char letter);
|
||||
char disk_get_next_free_letter(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,14 +6,43 @@
|
||||
#include "memory_manager.h"
|
||||
#include "io.h"
|
||||
#include "wm.h"
|
||||
#include "ahci.h"
|
||||
#include "../fs/vfs.h"
|
||||
#include "../fs/fat32.h"
|
||||
#include "../sys/spinlock.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_DISKS 26
|
||||
static spinlock_t ide_lock = SPINLOCK_INIT;
|
||||
|
||||
static Disk *disks[MAX_DISKS];
|
||||
static int disk_count = 0;
|
||||
static int next_drive_letter_idx = 0; // For backward compat
|
||||
static int next_sd_index = 0; // For sda, sdb, sdc...
|
||||
|
||||
// === ATA Definitions ===
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint64_t num);
|
||||
extern void log_ok(const char *msg);
|
||||
extern void log_fail(const char *msg);
|
||||
|
||||
// === String Helpers ===
|
||||
|
||||
static void dm_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static int dm_strcmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
static int dm_strlen(const char *s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
// === ATA Definitions (Legacy IDE PIO — kept as fallback) ===
|
||||
|
||||
#define ATA_PRIMARY_IO 0x1F0
|
||||
#define ATA_PRIMARY_CTRL 0x3F6
|
||||
@@ -35,91 +64,68 @@ static int disk_count = 0;
|
||||
#define ATA_CMD_WRITE_PIO 0x30
|
||||
#define ATA_CMD_IDENTIFY 0xEC
|
||||
|
||||
#define ATA_SR_BSY 0x80 // Busy
|
||||
#define ATA_SR_DRDY 0x40 // Drive ready
|
||||
#define ATA_SR_DF 0x20 // Drive write fault
|
||||
#define ATA_SR_DSC 0x10 // Drive seek complete
|
||||
#define ATA_SR_DRQ 0x08 // Data request ready
|
||||
#define ATA_SR_CORR 0x04 // Corrected data
|
||||
#define ATA_SR_IDX 0x02 // Index
|
||||
#define ATA_SR_ERR 0x01 // Error
|
||||
#define ATA_SR_BSY 0x80
|
||||
#define ATA_SR_DRDY 0x40
|
||||
#define ATA_SR_DF 0x20
|
||||
#define ATA_SR_DSC 0x10
|
||||
#define ATA_SR_DRQ 0x08
|
||||
#define ATA_SR_CORR 0x04
|
||||
#define ATA_SR_IDX 0x02
|
||||
#define ATA_SR_ERR 0x01
|
||||
|
||||
typedef struct {
|
||||
uint16_t port_base;
|
||||
bool slave;
|
||||
} ATADriverData;
|
||||
|
||||
// === Helpers ===
|
||||
// === ATA PIO Driver ===
|
||||
|
||||
static void dm_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
static int ata_wait_bsy(uint16_t port_base) {
|
||||
int timeout = 10000000;
|
||||
while ((inb(port_base + ATA_REG_STATUS) & ATA_SR_BSY) && --timeout > 0);
|
||||
return timeout <= 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
void disk_register(Disk *disk);
|
||||
|
||||
|
||||
static int ramdisk_read(Disk *disk, uint32_t sector, uint8_t *buffer) {
|
||||
(void)disk; (void)sector; (void)buffer;
|
||||
static int ata_wait_drq(uint16_t port_base) {
|
||||
int timeout = 10000000;
|
||||
while (!(inb(port_base + ATA_REG_STATUS) & (ATA_SR_DRQ | ATA_SR_ERR)) && --timeout > 0);
|
||||
if (timeout <= 0 || (inb(port_base + ATA_REG_STATUS) & ATA_SR_ERR)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ramdisk_write(Disk *disk, uint32_t sector, const uint8_t *buffer) {
|
||||
(void)disk; (void)sector; (void)buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void ata_wait_bsy(uint16_t port_base) {
|
||||
while (inb(port_base + ATA_REG_STATUS) & ATA_SR_BSY);
|
||||
}
|
||||
|
||||
static void ata_wait_drq(uint16_t port_base) {
|
||||
while (!(inb(port_base + ATA_REG_STATUS) & ATA_SR_DRQ));
|
||||
}
|
||||
|
||||
// Returns 1 if drive exists, 0 otherwise
|
||||
static int ata_identify(uint16_t port_base, bool slave) {
|
||||
// Select Drive
|
||||
outb(port_base + ATA_REG_HDDEVSEL, slave ? 0xB0 : 0xA0);
|
||||
// Zero out sector count and LBA registers
|
||||
outb(port_base + ATA_REG_SEC_COUNT0, 0);
|
||||
outb(port_base + ATA_REG_LBA0, 0);
|
||||
outb(port_base + ATA_REG_LBA1, 0);
|
||||
outb(port_base + ATA_REG_LBA2, 0);
|
||||
|
||||
// Send Identify command
|
||||
outb(port_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
|
||||
|
||||
// Check if status is 0 (no drive)
|
||||
uint8_t status = inb(port_base + ATA_REG_STATUS);
|
||||
if (status == 0) return 0;
|
||||
|
||||
// Wait until BSY clears
|
||||
int timeout = 10000;
|
||||
while ((inb(port_base + ATA_REG_STATUS) & ATA_SR_BSY) && --timeout > 0) {
|
||||
status = inb(port_base + ATA_REG_STATUS);
|
||||
if (status == 0) return 0; // Check again
|
||||
if (status == 0) return 0;
|
||||
}
|
||||
if (timeout <= 0) return 0; // Hardware didn't respond
|
||||
|
||||
// Check for error
|
||||
if (inb(port_base + ATA_REG_STATUS) & ATA_SR_ERR) {
|
||||
return 0; // Error, likely not ATA
|
||||
}
|
||||
|
||||
// Wait for DRQ or ERR
|
||||
while (!(inb(port_base + ATA_REG_STATUS) & (ATA_SR_DRQ | ATA_SR_ERR)));
|
||||
if (timeout <= 0) return 0;
|
||||
|
||||
if (inb(port_base + ATA_REG_STATUS) & ATA_SR_ERR) return 0;
|
||||
|
||||
// Read 256 words (512 bytes) of identity data
|
||||
if (ata_wait_drq(port_base) != 0) return 0;
|
||||
|
||||
if (inb(port_base + ATA_REG_STATUS) & ATA_SR_ERR) return 0;
|
||||
|
||||
uint32_t sectors = 0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
uint16_t data = inw(port_base + ATA_REG_DATA);
|
||||
(void)data;
|
||||
if (i == 60) sectors |= (uint32_t)data;
|
||||
if (i == 61) sectors |= (uint32_t)data << 16;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return sectors;
|
||||
}
|
||||
|
||||
static int ata_read_sector(Disk *disk, uint32_t lba, uint8_t *buffer) {
|
||||
@@ -127,9 +133,22 @@ static int ata_read_sector(Disk *disk, uint32_t lba, uint8_t *buffer) {
|
||||
uint16_t port_base = data->port_base;
|
||||
bool slave = data->slave;
|
||||
|
||||
ata_wait_bsy(port_base);
|
||||
// For partition reads, add the partition LBA offset
|
||||
if (disk->is_partition && disk->parent) {
|
||||
lba += disk->partition_lba_offset;
|
||||
// Use parent's driver
|
||||
data = (ATADriverData*)disk->parent->driver_data;
|
||||
port_base = data->port_base;
|
||||
slave = data->slave;
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&ide_lock);
|
||||
|
||||
if (ata_wait_bsy(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Select drive and send highest 4 bits of LBA
|
||||
outb(port_base + ATA_REG_HDDEVSEL, 0xE0 | (slave << 4) | ((lba >> 24) & 0x0F));
|
||||
outb(port_base + ATA_REG_FEATURES, 0x00);
|
||||
outb(port_base + ATA_REG_SEC_COUNT0, 1);
|
||||
@@ -138,15 +157,22 @@ static int ata_read_sector(Disk *disk, uint32_t lba, uint8_t *buffer) {
|
||||
outb(port_base + ATA_REG_LBA2, (uint8_t)(lba >> 16));
|
||||
outb(port_base + ATA_REG_COMMAND, ATA_CMD_READ_PIO);
|
||||
|
||||
ata_wait_bsy(port_base);
|
||||
ata_wait_drq(port_base);
|
||||
if (ata_wait_bsy(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
if (ata_wait_drq(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t *ptr = (uint16_t*)buffer;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
ptr[i] = inw(port_base + ATA_REG_DATA);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ata_write_sector(Disk *disk, uint32_t lba, const uint8_t *buffer) {
|
||||
@@ -154,7 +180,20 @@ static int ata_write_sector(Disk *disk, uint32_t lba, const uint8_t *buffer) {
|
||||
uint16_t port_base = data->port_base;
|
||||
bool slave = data->slave;
|
||||
|
||||
ata_wait_bsy(port_base);
|
||||
// For partition writes, add the partition LBA offset
|
||||
if (disk->is_partition && disk->parent) {
|
||||
lba += disk->partition_lba_offset;
|
||||
data = (ATADriverData*)disk->parent->driver_data;
|
||||
port_base = data->port_base;
|
||||
slave = data->slave;
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&ide_lock);
|
||||
|
||||
if (ata_wait_bsy(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
outb(port_base + ATA_REG_HDDEVSEL, 0xE0 | (slave << 4) | ((lba >> 24) & 0x0F));
|
||||
outb(port_base + ATA_REG_FEATURES, 0x00);
|
||||
@@ -164,74 +203,132 @@ static int ata_write_sector(Disk *disk, uint32_t lba, const uint8_t *buffer) {
|
||||
outb(port_base + ATA_REG_LBA2, (uint8_t)(lba >> 16));
|
||||
outb(port_base + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO);
|
||||
|
||||
ata_wait_bsy(port_base);
|
||||
ata_wait_drq(port_base);
|
||||
if (ata_wait_bsy(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
if (ata_wait_drq(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint16_t *ptr = (const uint16_t*)buffer;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
outw(port_base + ATA_REG_DATA, ptr[i]);
|
||||
}
|
||||
|
||||
// Flush / Sync
|
||||
outb(port_base + ATA_REG_COMMAND, 0xE7); // Cache Flush
|
||||
ata_wait_bsy(port_base);
|
||||
if (ata_wait_bsy(port_base) != 0) {
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&ide_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// === Device Naming ===
|
||||
|
||||
char disk_get_next_free_letter(void) {
|
||||
for (int i = 0; i < MAX_DISKS; i++) {
|
||||
char letter = 'A' + i;
|
||||
bool used = false;
|
||||
for (int j = 0; j < disk_count; j++) {
|
||||
if (disks[j]->letter == letter) {
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!used) return letter;
|
||||
}
|
||||
return 0; // No free letters
|
||||
const char* disk_get_next_dev_name(void) {
|
||||
static char name[8];
|
||||
name[0] = 's';
|
||||
name[1] = 'd';
|
||||
name[2] = 'a' + next_sd_index;
|
||||
name[3] = 0;
|
||||
next_sd_index++;
|
||||
return name;
|
||||
}
|
||||
|
||||
// === Registration ===
|
||||
|
||||
void disk_register(Disk *disk) {
|
||||
if (disk_count >= MAX_DISKS) return;
|
||||
|
||||
// Ensure letter is unique
|
||||
if (disk->letter == 0) {
|
||||
disk->letter = disk_get_next_free_letter();
|
||||
// Auto-assign devname if empty
|
||||
if (disk->devname[0] == 0) {
|
||||
const char *n = disk_get_next_dev_name();
|
||||
dm_strcpy(disk->devname, n);
|
||||
}
|
||||
|
||||
disk->registered = true;
|
||||
disks[disk_count++] = disk;
|
||||
|
||||
serial_write("[DISK] Registered /dev/");
|
||||
serial_write(disk->devname);
|
||||
serial_write(" (");
|
||||
serial_write(disk->label);
|
||||
serial_write(")\n");
|
||||
}
|
||||
|
||||
void disk_manager_init(void) {
|
||||
for (int i = 0; i < MAX_DISKS; i++) {
|
||||
disks[i] = NULL;
|
||||
void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count,
|
||||
bool is_fat32, int part_num) {
|
||||
if (disk_count >= MAX_DISKS) return;
|
||||
|
||||
Disk *part = (Disk*)kmalloc(sizeof(Disk));
|
||||
if (!part) return;
|
||||
|
||||
// Build name: parent_devname + partition number (e.g. "sda1")
|
||||
int len = dm_strlen(parent->devname);
|
||||
for (int i = 0; i < len; i++) part->devname[i] = parent->devname[i];
|
||||
part->devname[len] = '0' + part_num;
|
||||
part->devname[len + 1] = 0;
|
||||
|
||||
part->type = parent->type;
|
||||
part->is_fat32 = is_fat32;
|
||||
dm_strcpy(part->label, is_fat32 ? "FAT32 Partition" : "Unknown Partition");
|
||||
part->partition_lba_offset = lba_offset;
|
||||
part->total_sectors = sector_count;
|
||||
part->read_sector = parent->read_sector;
|
||||
part->write_sector = parent->write_sector;
|
||||
part->driver_data = parent->driver_data;
|
||||
part->parent = parent;
|
||||
part->is_partition = true;
|
||||
part->registered = true;
|
||||
|
||||
disks[disk_count++] = part;
|
||||
|
||||
serial_write("[DISK] Registered /dev/");
|
||||
serial_write(part->devname);
|
||||
serial_write(" (LBA offset ");
|
||||
serial_write_num(lba_offset);
|
||||
serial_write(", ");
|
||||
serial_write_num(sector_count);
|
||||
serial_write(" sectors, FAT32=");
|
||||
serial_write(" sectors, FAT32=");
|
||||
serial_write(is_fat32 ? "yes" : "no");
|
||||
serial_write(")\n");
|
||||
|
||||
if (is_fat32) {
|
||||
// Try to initialize and mount FAT32 volume to VFS
|
||||
void *vol = fat32_mount_volume(part);
|
||||
if (vol) {
|
||||
char mount_path[32];
|
||||
mount_path[0] = '/';
|
||||
mount_path[1] = 'd'; mount_path[2] = 'e'; mount_path[3] = 'v'; mount_path[4] = '/';
|
||||
dm_strcpy(mount_path + 5, part->devname);
|
||||
|
||||
if (vfs_mount(mount_path, part->devname, "fat32", fat32_get_realfs_ops(), vol)) {
|
||||
char ok_msg[64];
|
||||
dm_strcpy(ok_msg, "Mounted ");
|
||||
dm_strcpy(ok_msg + 8, mount_path);
|
||||
log_ok(ok_msg);
|
||||
wm_notify_fs_change();
|
||||
} else {
|
||||
char fail_msg[64];
|
||||
dm_strcpy(fail_msg, "Failed to mount ");
|
||||
dm_strcpy(fail_msg + 16, mount_path);
|
||||
log_fail(fail_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
disk_count = 0;
|
||||
|
||||
// Register A: (Ramdisk)
|
||||
Disk *ramdisk = (Disk*)kmalloc(sizeof(Disk));
|
||||
ramdisk->letter = 'A';
|
||||
ramdisk->type = DISK_TYPE_RAM;
|
||||
ramdisk->is_fat32 = true; // Ramdisk is always formatted
|
||||
dm_strcpy(ramdisk->name, "RAM");
|
||||
ramdisk->read_sector = ramdisk_read;
|
||||
ramdisk->write_sector = ramdisk_write;
|
||||
ramdisk->driver_data = NULL;
|
||||
ramdisk->partition_lba_offset = 0;
|
||||
|
||||
disk_register(ramdisk);
|
||||
}
|
||||
|
||||
Disk* disk_get_by_letter(char letter) {
|
||||
// Uppercase
|
||||
if (letter >= 'a' && letter <= 'z') letter -= 32;
|
||||
// === Lookup ===
|
||||
|
||||
Disk* disk_get_by_name(const char *devname) {
|
||||
if (!devname) return NULL;
|
||||
for (int i = 0; i < disk_count; i++) {
|
||||
if (disks[i]->letter == letter) {
|
||||
if (dm_strcmp(disks[i]->devname, devname) == 0) {
|
||||
return disks[i];
|
||||
}
|
||||
}
|
||||
@@ -247,36 +344,57 @@ Disk* disk_get_by_index(int index) {
|
||||
return disks[index];
|
||||
}
|
||||
|
||||
// === Backward Compat (deprecated) ===
|
||||
|
||||
// === MBR Partition Table Structures ===
|
||||
char disk_get_next_free_letter(void) {
|
||||
char letter = 'B' + next_drive_letter_idx++;
|
||||
if (letter > 'Z') return 0;
|
||||
return letter;
|
||||
}
|
||||
|
||||
Disk* disk_get_by_letter(char letter) {
|
||||
// Maps old letter scheme: A=ramfs (not a block device), B+=first real disk, etc.
|
||||
if (letter >= 'a' && letter <= 'z') letter -= 32;
|
||||
|
||||
// A: was the ramdisk — return NULL since ramfs is now VFS-managed
|
||||
if (letter == 'A') return NULL;
|
||||
|
||||
// B-Z map to disk indices 0, 1, 2...
|
||||
// Find real disks (non-RAM, non-partition-parent)
|
||||
int real_idx = 0;
|
||||
for (int i = 0; i < disk_count; i++) {
|
||||
if (disks[i]->is_partition && disks[i]->is_fat32) {
|
||||
if (real_idx == (letter - 'B')) {
|
||||
return disks[i];
|
||||
}
|
||||
real_idx++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// === MBR Partition Table ===
|
||||
|
||||
typedef struct {
|
||||
uint8_t status; // 0x80 = bootable, 0x00 = inactive
|
||||
uint8_t chs_first[3]; // CHS of first sector
|
||||
uint8_t type; // Partition type
|
||||
uint8_t chs_last[3]; // CHS of last sector
|
||||
uint32_t lba_start; // LBA of first sector
|
||||
uint32_t sector_count; // Number of sectors
|
||||
uint8_t status;
|
||||
uint8_t chs_first[3];
|
||||
uint8_t type;
|
||||
uint8_t chs_last[3];
|
||||
uint32_t lba_start;
|
||||
uint32_t sector_count;
|
||||
} __attribute__((packed)) MBR_PartitionEntry;
|
||||
|
||||
// FAT32 partition type codes
|
||||
#define PART_TYPE_FAT32 0x0B
|
||||
#define PART_TYPE_FAT32_LBA 0x0C
|
||||
|
||||
// Check if sector contains a valid FAT32 BPB (Volume Boot Record)
|
||||
static bool is_fat32_bpb(const uint8_t *sector) {
|
||||
// Must have 0xAA55 boot signature
|
||||
if (sector[510] != 0x55 || sector[511] != 0xAA) return false;
|
||||
|
||||
// Check for FAT32 filesystem string at offset 82
|
||||
// "FAT32 " in the fs_type field of the BPB
|
||||
if (sector[82] == 'F' && sector[83] == 'A' && sector[84] == 'T' &&
|
||||
sector[85] == '3' && sector[86] == '2') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also accept if bytes_per_sector is 512 and sectors_per_fat_16 is 0
|
||||
// (FAT32 always has sectors_per_fat_16 == 0)
|
||||
uint16_t bps = *(uint16_t*)§or[11];
|
||||
uint16_t spf16 = *(uint16_t*)§or[22];
|
||||
uint32_t spf32 = *(uint32_t*)§or[36];
|
||||
@@ -287,60 +405,68 @@ static bool is_fat32_bpb(const uint8_t *sector) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse MBR partition table and find a FAT32 partition.
|
||||
// Sets disk->partition_lba_offset and returns true if found.
|
||||
static bool detect_fat32_partition(Disk *disk) {
|
||||
// Parse MBR and register each partition as a child block device
|
||||
static void parse_mbr_partitions(Disk *disk) {
|
||||
uint8_t *buffer = (uint8_t*)kmalloc(512);
|
||||
if (!buffer) return false;
|
||||
if (!buffer) return;
|
||||
|
||||
// Read sector 0 (MBR or raw BPB)
|
||||
if (disk->read_sector(disk, 0, buffer) != 0) {
|
||||
kfree(buffer);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Must have 0xAA55 boot signature
|
||||
// Check for valid MBR signature
|
||||
if (buffer[510] != 0x55 || buffer[511] != 0xAA) {
|
||||
kfree(buffer);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check MBR partition table entries (4 entries at offset 446)
|
||||
MBR_PartitionEntry *partitions = (MBR_PartitionEntry*)&buffer[446];
|
||||
int part_num = 1;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (partitions[i].type == PART_TYPE_FAT32 ||
|
||||
partitions[i].type == PART_TYPE_FAT32_LBA) {
|
||||
uint32_t start = partitions[i].lba_start;
|
||||
uint32_t size = partitions[i].sector_count;
|
||||
uint8_t type = partitions[i].type;
|
||||
|
||||
uint32_t part_lba = partitions[i].lba_start;
|
||||
if (type == 0x00) continue; // Empty entry
|
||||
if (size == 0) continue;
|
||||
if (start >= disk->total_sectors) continue; // Invalid start
|
||||
|
||||
// Read the partition's first sector to verify it's a valid FAT32 BPB
|
||||
bool fat32 = false;
|
||||
if (type == PART_TYPE_FAT32 || type == PART_TYPE_FAT32_LBA) {
|
||||
// Verify by reading the BPB
|
||||
uint8_t *pbuf = (uint8_t*)kmalloc(512);
|
||||
if (!pbuf) { kfree(buffer); return false; }
|
||||
|
||||
if (disk->read_sector(disk, part_lba, pbuf) == 0 && is_fat32_bpb(pbuf)) {
|
||||
disk->partition_lba_offset = part_lba;
|
||||
if (pbuf) {
|
||||
if (disk->read_sector(disk, start, pbuf) == 0) {
|
||||
fat32 = is_fat32_bpb(pbuf);
|
||||
}
|
||||
kfree(pbuf);
|
||||
kfree(buffer);
|
||||
return true;
|
||||
}
|
||||
kfree(pbuf);
|
||||
}
|
||||
|
||||
disk_register_partition(disk, partitions[i].lba_start,
|
||||
partitions[i].sector_count, fat32, part_num);
|
||||
part_num++;
|
||||
}
|
||||
|
||||
// Fallback: check if sector 0 itself is a raw FAT32 BPB (no partition table)
|
||||
if (is_fat32_bpb(buffer)) {
|
||||
// Fallback: if no partitions found, check if entire disk is a raw FAT32 volume
|
||||
if (part_num == 1 && is_fat32_bpb(buffer)) {
|
||||
serial_write("[DISK] No MBR partitions — raw FAT32 volume on /dev/");
|
||||
serial_write(disk->devname);
|
||||
serial_write("\n");
|
||||
disk->is_fat32 = true;
|
||||
disk->partition_lba_offset = 0;
|
||||
kfree(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
kfree(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// === ATA Drive Discovery ===
|
||||
|
||||
static void try_add_ata_drive(uint16_t port, bool slave, const char *name) {
|
||||
if (ata_identify(port, slave)) {
|
||||
uint32_t sectors = ata_identify(port, slave);
|
||||
if (sectors > 0) {
|
||||
Disk *new_disk = (Disk*)kmalloc(sizeof(Disk));
|
||||
if (!new_disk) return;
|
||||
|
||||
@@ -348,29 +474,52 @@ static void try_add_ata_drive(uint16_t port, bool slave, const char *name) {
|
||||
data->port_base = port;
|
||||
data->slave = slave;
|
||||
|
||||
new_disk->letter = 0; // Auto-assign
|
||||
new_disk->devname[0] = 0; // Auto-assign
|
||||
new_disk->type = DISK_TYPE_IDE;
|
||||
dm_strcpy(new_disk->name, name);
|
||||
dm_strcpy(new_disk->label, name);
|
||||
new_disk->read_sector = ata_read_sector;
|
||||
new_disk->write_sector = ata_write_sector;
|
||||
new_disk->driver_data = data;
|
||||
new_disk->partition_lba_offset = 0;
|
||||
new_disk->total_sectors = sectors;
|
||||
new_disk->parent = NULL;
|
||||
new_disk->is_partition = false;
|
||||
new_disk->is_fat32 = false;
|
||||
|
||||
// Detect FAT32 (with MBR partition support)
|
||||
if (detect_fat32_partition(new_disk)) {
|
||||
new_disk->is_fat32 = true;
|
||||
disk_register(new_disk);
|
||||
} else {
|
||||
kfree(data);
|
||||
kfree(new_disk);
|
||||
}
|
||||
disk_register(new_disk);
|
||||
|
||||
// Parse MBR to find partitions
|
||||
parse_mbr_partitions(new_disk);
|
||||
}
|
||||
}
|
||||
|
||||
void disk_manager_scan(void) {
|
||||
// Probe Standard ATA Ports
|
||||
try_add_ata_drive(ATA_PRIMARY_IO, false, "IDE1");
|
||||
try_add_ata_drive(ATA_PRIMARY_IO, true, "IDE2");
|
||||
try_add_ata_drive(ATA_SECONDARY_IO, false, "IDE3");
|
||||
try_add_ata_drive(ATA_SECONDARY_IO, true, "IDE4");
|
||||
// === Init & Scan ===
|
||||
|
||||
void disk_manager_init(void) {
|
||||
for (int i = 0; i < MAX_DISKS; i++) {
|
||||
disks[i] = NULL;
|
||||
}
|
||||
disk_count = 0;
|
||||
next_sd_index = 0;
|
||||
next_drive_letter_idx = 0;
|
||||
|
||||
log_ok("Disk manager ready");
|
||||
// NOTE: Ramdisk (A:) is no longer registered here.
|
||||
// RAMFS is managed directly by fat32.c and mounted at "/" via VFS.
|
||||
}
|
||||
|
||||
void disk_manager_scan(void) {
|
||||
serial_write("[DISK] Initializing AHCI (SATA DMA)...\n");
|
||||
ahci_init();
|
||||
|
||||
if (ahci_get_port_count() == 0) {
|
||||
serial_write("[DISK] No AHCI ports found, falling back to legacy IDE...\n");
|
||||
try_add_ata_drive(ATA_PRIMARY_IO, false, "IDE Primary Master");
|
||||
try_add_ata_drive(ATA_PRIMARY_IO, true, "IDE Primary Slave");
|
||||
try_add_ata_drive(ATA_SECONDARY_IO, false, "IDE Secondary Master");
|
||||
try_add_ata_drive(ATA_SECONDARY_IO, true, "IDE Secondary Slave");
|
||||
log_ok("IDE probing complete");
|
||||
} else {
|
||||
log_ok("AHCI ports initialized, skipping IDE");
|
||||
}
|
||||
}
|
||||
@@ -97,3 +97,23 @@ int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t*
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t pci_get_bar(pci_device_t *dev, int bar_num) {
|
||||
if (!dev || bar_num < 0 || bar_num > 5) return 0;
|
||||
uint8_t offset = 0x10 + (bar_num * 4);
|
||||
return pci_read_config(dev->bus, dev->device, dev->function, offset);
|
||||
}
|
||||
|
||||
void pci_enable_bus_mastering(pci_device_t *dev) {
|
||||
if (!dev) return;
|
||||
uint32_t cmd = pci_read_config(dev->bus, dev->device, dev->function, 0x04);
|
||||
cmd |= (1 << 2); // Set Bus Master bit
|
||||
pci_write_config(dev->bus, dev->device, dev->function, 0x04, cmd);
|
||||
}
|
||||
|
||||
void pci_enable_mmio(pci_device_t *dev) {
|
||||
if (!dev) return;
|
||||
uint32_t cmd = pci_read_config(dev->bus, dev->device, dev->function, 0x04);
|
||||
cmd |= (1 << 1); // Set Memory Space bit
|
||||
pci_write_config(dev->bus, dev->device, dev->function, 0x04, cmd);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ typedef struct {
|
||||
|
||||
#define PCI_CLASS_NETWORK_CONTROLLER 0x02
|
||||
#define PCI_CLASS_ETHERNET_CONTROLLER 0x00
|
||||
#define PCI_CLASS_MASS_STORAGE 0x01
|
||||
#define PCI_SUBCLASS_SATA 0x06
|
||||
#define PCI_SUBCLASS_IDE 0x01
|
||||
|
||||
uint32_t pci_read_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
|
||||
void pci_write_config(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
|
||||
@@ -35,4 +38,9 @@ int pci_enumerate_devices(pci_device_t* devices, int max_devices);
|
||||
int pci_find_device(uint16_t vendor_id, uint16_t device_id, pci_device_t* device);
|
||||
int pci_find_device_by_class(uint8_t class_code, uint8_t subclass, pci_device_t* device);
|
||||
|
||||
// BAR access and bus mastering helpers
|
||||
uint32_t pci_get_bar(pci_device_t *dev, int bar_num);
|
||||
void pci_enable_bus_mastering(pci_device_t *dev);
|
||||
void pci_enable_mmio(pci_device_t *dev);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,18 +16,19 @@ extern void serial_print_hex(uint64_t n);
|
||||
volatile uint64_t kernel_ticks = 0;
|
||||
|
||||
uint64_t timer_handler(registers_t *regs) {
|
||||
kernel_ticks++;
|
||||
wm_timer_tick();
|
||||
network_process_frames();
|
||||
if (smp_this_cpu_id() == 0) {
|
||||
kernel_ticks++;
|
||||
wm_timer_tick();
|
||||
network_process_frames();
|
||||
|
||||
extern void k_beep_process(void);
|
||||
k_beep_process();
|
||||
extern void k_beep_process(void);
|
||||
k_beep_process();
|
||||
}
|
||||
|
||||
outb(0x20, 0x20); // EOI after processing to prevent nested timer interrupts
|
||||
outb(0x20, 0x20);
|
||||
extern uint64_t process_schedule(uint64_t current_rsp);
|
||||
uint64_t new_rsp = process_schedule((uint64_t)regs);
|
||||
|
||||
// SMP: Wake AP cores to run their assigned processes
|
||||
if (smp_cpu_count() > 1) {
|
||||
lapic_send_ipi_all();
|
||||
}
|
||||
@@ -37,9 +38,27 @@ uint64_t timer_handler(registers_t *regs) {
|
||||
|
||||
// --- Keyboard ---
|
||||
static bool shift_pressed = false;
|
||||
static bool caps_lock_on = false;
|
||||
bool ps2_ctrl_pressed = false;
|
||||
static bool extended_scancode = false;
|
||||
|
||||
static void ps2_kbd_wait_write(void) {
|
||||
uint32_t timeout = 100000;
|
||||
while (timeout--) {
|
||||
if ((inb(0x64) & 2) == 0) return;
|
||||
}
|
||||
}
|
||||
|
||||
static void ps2_update_leds(void) {
|
||||
uint8_t led_status = 0;
|
||||
if (caps_lock_on) led_status |= 4;
|
||||
|
||||
ps2_kbd_wait_write();
|
||||
outb(0x60, 0xED);
|
||||
ps2_kbd_wait_write();
|
||||
outb(0x60, led_status);
|
||||
}
|
||||
|
||||
static char scancode_map[128] = {
|
||||
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
|
||||
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
|
||||
@@ -67,30 +86,22 @@ uint64_t keyboard_handler(registers_t *regs) {
|
||||
|
||||
if (scancode == 0x1D) {
|
||||
ps2_ctrl_pressed = true;
|
||||
extended_scancode = false; // Reset if Ctrl is pressed (prevents E0 1D bug)
|
||||
extended_scancode = false;
|
||||
} else if (scancode == 0x9D) {
|
||||
ps2_ctrl_pressed = false;
|
||||
extended_scancode = false;
|
||||
}
|
||||
|
||||
if (ps2_ctrl_pressed && scancode == 0x2E) {
|
||||
extern process_t* process_get_current(void);
|
||||
process_t* proc = process_get_current();
|
||||
if (proc && proc->is_user && proc->is_terminal_proc && proc->ui_window) {
|
||||
// Only kill if the associated terminal window is focused
|
||||
if (((Window*)proc->ui_window)->focused) {
|
||||
extern uint64_t process_terminate_current(void);
|
||||
outb(0x20, 0x20); // EOI before context switch
|
||||
return process_terminate_current();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (scancode == 0x2A || scancode == 0x36) { // Shift Down
|
||||
shift_pressed = true;
|
||||
} else if (scancode == 0xAA || scancode == 0xB6) { // Shift Up
|
||||
shift_pressed = false;
|
||||
} else if (!(scancode & 0x80)) { // Key Press (not release)
|
||||
} else if (scancode == 0x3A) { // Caps Lock Down
|
||||
caps_lock_on = !caps_lock_on;
|
||||
ps2_update_leds();
|
||||
} else if (!(scancode & 0x80)) { // Key Press
|
||||
if (extended_scancode) {
|
||||
extended_scancode = false;
|
||||
switch (scancode) {
|
||||
@@ -102,6 +113,10 @@ uint64_t keyboard_handler(registers_t *regs) {
|
||||
} else {
|
||||
char c = shift_pressed ? scancode_map_shift[scancode] : scancode_map[scancode];
|
||||
if (c) {
|
||||
if (caps_lock_on) {
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
else if (c >= 'A' && c <= 'Z') c += 32;
|
||||
}
|
||||
wm_handle_key(c, true);
|
||||
}
|
||||
}
|
||||
@@ -115,8 +130,13 @@ uint64_t keyboard_handler(registers_t *regs) {
|
||||
case 0x4D: wm_handle_key(20, false); break; // Right arrow
|
||||
}
|
||||
} else {
|
||||
char c = shift_pressed ? scancode_map_shift[scancode & 0x7F] : scancode_map[scancode & 0x7F];
|
||||
uint8_t base_scancode = scancode & 0x7F;
|
||||
char c = shift_pressed ? scancode_map_shift[base_scancode] : scancode_map[base_scancode];
|
||||
if (c) {
|
||||
if (caps_lock_on) {
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
else if (c >= 'A' && c <= 'Z') c += 32;
|
||||
}
|
||||
wm_handle_key(c, false);
|
||||
}
|
||||
}
|
||||
@@ -177,7 +197,7 @@ void mouse_init(void) {
|
||||
mouse_write(0xF6);
|
||||
mouse_read();
|
||||
|
||||
// Enable Wheel - Magic Sequence
|
||||
// Enable Wheel
|
||||
mouse_write(0xF3); mouse_read(); mouse_write(200); mouse_read();
|
||||
mouse_write(0xF3); mouse_read(); mouse_write(100); mouse_read();
|
||||
mouse_write(0xF3); mouse_read(); mouse_write(80); mouse_read();
|
||||
@@ -239,3 +259,7 @@ uint64_t mouse_handler(registers_t *regs) {
|
||||
void ps2_init(void) {
|
||||
mouse_init();
|
||||
}
|
||||
|
||||
bool ps2_shift_pressed(void) {
|
||||
return shift_pressed;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ uint64_t timer_handler(registers_t *regs);
|
||||
uint64_t keyboard_handler(registers_t *regs);
|
||||
uint64_t mouse_handler(registers_t *regs);
|
||||
|
||||
bool ps2_shift_pressed(void);
|
||||
|
||||
#endif
|
||||
|
||||
541
src/fs/bootfs.c
Normal file
541
src/fs/bootfs.c
Normal file
@@ -0,0 +1,541 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "bootfs.h"
|
||||
#include "../sys/bootfs_state.h"
|
||||
#include "vfs.h"
|
||||
#include "core/kutils.h"
|
||||
#include "core/platform.h"
|
||||
#include "core/kconsole.h"
|
||||
#include "memory_manager.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_hex(uint64_t value);
|
||||
|
||||
typedef struct {
|
||||
char path[512];
|
||||
int offset;
|
||||
bool is_root;
|
||||
bool is_metadata_dir;
|
||||
} bootfs_handle_t;
|
||||
|
||||
static void* bootfs_open(void *fs_private, const char *path, const char *mode);
|
||||
static void bootfs_close(void *fs_private, void *handle);
|
||||
static int bootfs_read(void *fs_private, void *handle, void *buf, int size);
|
||||
static int bootfs_write(void *fs_private, void *handle, const void *buf, int size);
|
||||
static int bootfs_seek(void *fs_private, void *handle, int offset, int whence);
|
||||
static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max);
|
||||
static bool bootfs_mkdir(void *fs_private, const char *rel_path);
|
||||
static bool bootfs_rmdir(void *fs_private, const char *rel_path);
|
||||
static bool bootfs_unlink(void *fs_private, const char *rel_path);
|
||||
static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path);
|
||||
static bool bootfs_exists(void *fs_private, const char *rel_path);
|
||||
static bool bootfs_is_dir(void *fs_private, const char *rel_path);
|
||||
static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info);
|
||||
static uint32_t bootfs_get_position(void *file_handle);
|
||||
static uint32_t bootfs_get_size(void *file_handle);
|
||||
|
||||
static vfs_fs_ops_t bootfs_ops = {
|
||||
.open = bootfs_open,
|
||||
.close = bootfs_close,
|
||||
.read = bootfs_read,
|
||||
.write = bootfs_write,
|
||||
.seek = bootfs_seek,
|
||||
.readdir = bootfs_readdir,
|
||||
.mkdir = bootfs_mkdir,
|
||||
.rmdir = bootfs_rmdir,
|
||||
.unlink = bootfs_unlink,
|
||||
.rename = bootfs_rename,
|
||||
.exists = bootfs_exists,
|
||||
.is_dir = bootfs_is_dir,
|
||||
.get_info = bootfs_get_info,
|
||||
.get_position = bootfs_get_position,
|
||||
.get_size = bootfs_get_size,
|
||||
};
|
||||
|
||||
bootfs_state_t g_bootfs_state = {0};
|
||||
|
||||
static bool is_metadata_path(const char *path) {
|
||||
if (!path) return false;
|
||||
return k_strncmp(path, "metadata", 8) == 0;
|
||||
}
|
||||
|
||||
static bool is_metadata_file(const char *path) {
|
||||
if (k_strcmp(path, "metadata/boot_time") == 0) return true;
|
||||
if (k_strcmp(path, "metadata/boot_flags") == 0) return true;
|
||||
if (k_strcmp(path, "metadata/version") == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* bootfs_open(void *fs_private, const char *path, const char *mode) {
|
||||
if (!path) path = "";
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)kmalloc(sizeof(bootfs_handle_t));
|
||||
if (!h) return NULL;
|
||||
|
||||
k_memset(h, 0, sizeof(bootfs_handle_t));
|
||||
k_strcpy(h->path, path);
|
||||
h->offset = 0;
|
||||
|
||||
if (path[0] == '\0') {
|
||||
h->is_root = true;
|
||||
} else if (is_metadata_path(path) && path[8] == '\0') {
|
||||
h->is_metadata_dir = true;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static void bootfs_close(void *fs_private, void *handle) {
|
||||
if (handle) kfree(handle);
|
||||
}
|
||||
|
||||
static int generate_metadata_content(const char *file, char *buffer, int max_size) {
|
||||
if (!buffer || max_size <= 0) return 0;
|
||||
|
||||
buffer[0] = '\0';
|
||||
int len = 0;
|
||||
|
||||
if (k_strcmp(file, "metadata/boot_time") == 0) {
|
||||
extern uint32_t wm_get_ticks(void);
|
||||
uint32_t ticks = wm_get_ticks();
|
||||
|
||||
k_strcpy(buffer, "Boot time: ");
|
||||
char time_buf[32];
|
||||
k_itoa(g_bootfs_state.boot_time_ms, time_buf);
|
||||
k_strcpy(buffer + k_strlen(buffer), time_buf);
|
||||
k_strcpy(buffer + k_strlen(buffer), " ms\nTicks: ");
|
||||
k_itoa(ticks, time_buf);
|
||||
k_strcpy(buffer + k_strlen(buffer), time_buf);
|
||||
k_strcpy(buffer + k_strlen(buffer), "\n");
|
||||
len = k_strlen(buffer);
|
||||
} else if (k_strcmp(file, "metadata/version") == 0) {
|
||||
k_strcpy(buffer, "Bootloader: ");
|
||||
k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_name);
|
||||
k_strcpy(buffer + k_strlen(buffer), "\nVersion: ");
|
||||
k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_version);
|
||||
k_strcpy(buffer + k_strlen(buffer), "\n");
|
||||
len = k_strlen(buffer);
|
||||
} else if (k_strcmp(file, "metadata/boot_flags") == 0) {
|
||||
k_strcpy(buffer, "Boot flags: 0x");
|
||||
char flags_buf[8];
|
||||
uint8_t flags = g_bootfs_state.boot_flags;
|
||||
int hex_digit = (flags >> 4) & 0xF;
|
||||
flags_buf[0] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10);
|
||||
hex_digit = flags & 0xF;
|
||||
flags_buf[1] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10);
|
||||
flags_buf[2] = '\n';
|
||||
flags_buf[3] = '\0';
|
||||
k_strcpy(buffer + k_strlen(buffer), flags_buf);
|
||||
len = k_strlen(buffer);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int bootfs_read(void *fs_private, void *handle, void *buf, int size) {
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)handle;
|
||||
if (!h || !buf || size <= 0) return -1;
|
||||
|
||||
char *content_buffer = (char*)kmalloc(4096);
|
||||
if (!content_buffer) return -1;
|
||||
|
||||
int content_len = 0;
|
||||
|
||||
if (k_strcmp(h->path, "limine.conf") == 0) {
|
||||
k_memcpy(content_buffer, g_bootfs_state.limine_conf,
|
||||
g_bootfs_state.limine_conf_len);
|
||||
content_len = g_bootfs_state.limine_conf_len;
|
||||
} else if (k_strcmp(h->path, "kernel") == 0) {
|
||||
k_strcpy(content_buffer, "Kernel reference\nSize: ");
|
||||
char size_buf[32];
|
||||
k_itoa(g_bootfs_state.kernel_size, size_buf);
|
||||
k_strcpy(content_buffer + k_strlen(content_buffer), size_buf);
|
||||
k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n");
|
||||
content_len = k_strlen(content_buffer);
|
||||
} else if (k_strcmp(h->path, "initrd") == 0) {
|
||||
k_strcpy(content_buffer, "Initial ramdisk reference\nSize: ");
|
||||
char size_buf[32];
|
||||
k_itoa(g_bootfs_state.initrd_size, size_buf);
|
||||
k_strcpy(content_buffer + k_strlen(content_buffer), size_buf);
|
||||
k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n");
|
||||
content_len = k_strlen(content_buffer);
|
||||
} else if (is_metadata_file(h->path)) {
|
||||
content_len = generate_metadata_content(h->path, content_buffer, 4096);
|
||||
} else {
|
||||
kfree(content_buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle offset and reading
|
||||
if (h->offset >= content_len) {
|
||||
kfree(content_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int available = content_len - h->offset;
|
||||
int read_size = (available < size) ? available : size;
|
||||
|
||||
k_memcpy(buf, content_buffer + h->offset, read_size);
|
||||
h->offset += read_size;
|
||||
|
||||
kfree(content_buffer);
|
||||
return read_size;
|
||||
}
|
||||
|
||||
static int bootfs_write(void *fs_private, void *handle, const void *buf, int size) {
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)handle;
|
||||
if (!h || !buf || size <= 0) return -1;
|
||||
|
||||
if (k_strcmp(h->path, "limine.conf") != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int max_write = 2048 - h->offset;
|
||||
if (max_write <= 0) return -1;
|
||||
|
||||
int write_size = (size < max_write) ? size : max_write;
|
||||
k_memcpy(g_bootfs_state.limine_conf + h->offset, buf, write_size);
|
||||
h->offset += write_size;
|
||||
|
||||
if (h->offset > g_bootfs_state.limine_conf_len) {
|
||||
g_bootfs_state.limine_conf_len = h->offset;
|
||||
}
|
||||
|
||||
extern vfs_file_t* vfs_open(const char *path, const char *mode);
|
||||
extern int vfs_write(vfs_file_t *file, const void *buf, int size);
|
||||
extern void vfs_close(vfs_file_t *file);
|
||||
|
||||
vfs_file_t *fat_conf = vfs_open("/limine.conf", "w");
|
||||
if (fat_conf) {
|
||||
vfs_write(fat_conf, g_bootfs_state.limine_conf, g_bootfs_state.limine_conf_len);
|
||||
vfs_close(fat_conf);
|
||||
}
|
||||
|
||||
return write_size;
|
||||
}
|
||||
|
||||
static int bootfs_seek(void *fs_private, void *handle, int offset, int whence) {
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)handle;
|
||||
if (!h) return -1;
|
||||
|
||||
switch (whence) {
|
||||
case 0: // SEEK_SET
|
||||
h->offset = offset;
|
||||
break;
|
||||
case 1: // SEEK_CUR
|
||||
h->offset += offset;
|
||||
break;
|
||||
case 2: // SEEK_END
|
||||
return -1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return h->offset;
|
||||
}
|
||||
|
||||
static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max) {
|
||||
if (!entries || max <= 0) return 0;
|
||||
|
||||
if (!rel_path) rel_path = "";
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
int count = 0;
|
||||
|
||||
if (rel_path[0] == '\0') {
|
||||
if (count < max) {
|
||||
k_strcpy(entries[count].name, "limine.conf");
|
||||
entries[count].size = g_bootfs_state.limine_conf_len;
|
||||
entries[count].is_directory = 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count < max) {
|
||||
k_strcpy(entries[count].name, "kernel");
|
||||
entries[count].size = g_bootfs_state.kernel_size;
|
||||
entries[count].is_directory = 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count < max) {
|
||||
k_strcpy(entries[count].name, "initrd");
|
||||
entries[count].size = g_bootfs_state.initrd_size;
|
||||
entries[count].is_directory = 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count < max) {
|
||||
k_strcpy(entries[count].name, "metadata");
|
||||
entries[count].size = 0;
|
||||
entries[count].is_directory = 1;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else if (k_strcmp(rel_path, "metadata") == 0) {
|
||||
const char *meta_files[] = {
|
||||
"boot_time",
|
||||
"boot_flags",
|
||||
"version"
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3 && count < max; i++) {
|
||||
k_strcpy(entries[count].name, meta_files[i]);
|
||||
entries[count].size = 0;
|
||||
entries[count].is_directory = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool bootfs_mkdir(void *fs_private, const char *rel_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bootfs_rmdir(void *fs_private, const char *rel_path) {
|
||||
if (!rel_path) rel_path = "";
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
if (k_strcmp(rel_path, "metadata") == 0) {
|
||||
return false; /* metadata directory is protected */
|
||||
}
|
||||
|
||||
return false; /* no other directories to remove */
|
||||
}
|
||||
|
||||
static bool bootfs_unlink(void *fs_private, const char *rel_path) {
|
||||
if (!rel_path) return false;
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
/* Only limine.conf can be deleted */
|
||||
if (k_strcmp(rel_path, "limine.conf") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Clear the bootfs state */
|
||||
g_bootfs_state.limine_conf[0] = '\0';
|
||||
g_bootfs_state.limine_conf_len = 0;
|
||||
|
||||
/* Delete from partition */
|
||||
extern bool vfs_delete(const char *path);
|
||||
|
||||
bool result = vfs_delete("/limine.conf");
|
||||
|
||||
if (result) {
|
||||
serial_write("[BOOTFS] Deleted limine.conf from partition\n");
|
||||
} else {
|
||||
serial_write("[BOOTFS] Warning: Could not delete limine.conf from partition\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path) {
|
||||
if (!old_path || !new_path) return false;
|
||||
|
||||
const char *old_rel = old_path;
|
||||
const char *new_rel = new_path;
|
||||
|
||||
if (old_rel[0] == '/') old_rel++;
|
||||
if (new_rel[0] == '/') new_rel++;
|
||||
|
||||
/* Only limine.conf can be renamed */
|
||||
if (k_strcmp(old_rel, "limine.conf") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* kernel and initrd are protected */
|
||||
if (k_strcmp(new_rel, "kernel") == 0 || k_strcmp(new_rel, "initrd") == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* metadata directory is protected */
|
||||
if (k_strncmp(new_rel, "metadata", 8) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
extern bool vfs_rename(const char *old_path, const char *new_path);
|
||||
|
||||
char new_partition_path[256];
|
||||
k_strcpy(new_partition_path, "/");
|
||||
|
||||
/* Manually append new_rel to new_partition_path */
|
||||
int path_len = 0;
|
||||
while (new_partition_path[path_len]) path_len++;
|
||||
|
||||
int rel_len = 0;
|
||||
while (new_rel[rel_len]) rel_len++;
|
||||
|
||||
if (path_len + rel_len >= 256) {
|
||||
serial_write("[BOOTFS] Error: new path too long\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
k_memcpy(new_partition_path + path_len, new_rel, rel_len + 1);
|
||||
|
||||
/* Rename on partition filesystem */
|
||||
bool result = vfs_rename("/limine.conf", new_partition_path);
|
||||
|
||||
if (result) {
|
||||
serial_write("[BOOTFS] Renamed limine.conf to ");
|
||||
serial_write(new_rel);
|
||||
serial_write("\n");
|
||||
} else {
|
||||
serial_write("[BOOTFS] Warning: Could not rename limine.conf to ");
|
||||
serial_write(new_rel);
|
||||
serial_write("\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool bootfs_exists(void *fs_private, const char *rel_path) {
|
||||
if (!rel_path) rel_path = "";
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
if (rel_path[0] == '\0') return true;
|
||||
|
||||
if (k_strcmp(rel_path, "limine.conf") == 0) return true;
|
||||
if (k_strcmp(rel_path, "kernel") == 0) return true;
|
||||
if (k_strcmp(rel_path, "initrd") == 0) return true;
|
||||
|
||||
if (k_strcmp(rel_path, "metadata") == 0) return true;
|
||||
if (is_metadata_file(rel_path)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bootfs_is_dir(void *fs_private, const char *rel_path) {
|
||||
if (!rel_path) rel_path = "";
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
if (rel_path[0] == '\0') return true;
|
||||
if (k_strcmp(rel_path, "metadata") == 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info) {
|
||||
if (!info) return -1;
|
||||
if (!rel_path) rel_path = "";
|
||||
if (rel_path[0] == '/') rel_path++;
|
||||
|
||||
k_memset(info, 0, sizeof(vfs_dirent_t));
|
||||
|
||||
if (rel_path[0] == '\0') {
|
||||
k_strcpy(info->name, "/");
|
||||
info->is_directory = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (k_strcmp(rel_path, "limine.conf") == 0) {
|
||||
k_strcpy(info->name, "limine.conf");
|
||||
info->size = g_bootfs_state.limine_conf_len;
|
||||
info->is_directory = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (k_strcmp(rel_path, "kernel") == 0) {
|
||||
k_strcpy(info->name, "kernel");
|
||||
info->size = g_bootfs_state.kernel_size;
|
||||
info->is_directory = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (k_strcmp(rel_path, "initrd") == 0) {
|
||||
k_strcpy(info->name, "initrd");
|
||||
info->size = g_bootfs_state.initrd_size;
|
||||
info->is_directory = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (k_strcmp(rel_path, "metadata") == 0) {
|
||||
k_strcpy(info->name, "metadata");
|
||||
info->is_directory = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_metadata_file(rel_path)) {
|
||||
char temp_buf[4096];
|
||||
int len = generate_metadata_content(rel_path, temp_buf, 4096);
|
||||
k_strcpy(info->name, rel_path + 9);
|
||||
info->size = len;
|
||||
info->is_directory = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint32_t bootfs_get_position(void *file_handle) {
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)file_handle;
|
||||
if (!h) return 0;
|
||||
return h->offset;
|
||||
}
|
||||
|
||||
static uint32_t bootfs_get_size(void *file_handle) {
|
||||
bootfs_handle_t *h = (bootfs_handle_t*)file_handle;
|
||||
if (!h) return 0;
|
||||
|
||||
if (k_strcmp(h->path, "limine.conf") == 0) {
|
||||
return g_bootfs_state.limine_conf_len;
|
||||
} else if (k_strcmp(h->path, "kernel") == 0) {
|
||||
return g_bootfs_state.kernel_size;
|
||||
} else if (k_strcmp(h->path, "initrd") == 0) {
|
||||
return g_bootfs_state.initrd_size;
|
||||
} else if (is_metadata_file(h->path)) {
|
||||
char temp_buf[4096];
|
||||
return generate_metadata_content(h->path, temp_buf, 4096);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vfs_fs_ops_t* bootfs_get_ops(void) {
|
||||
return &bootfs_ops;
|
||||
}
|
||||
|
||||
void bootfs_state_init(void) {
|
||||
k_memset(&g_bootfs_state, 0, sizeof(bootfs_state_t));
|
||||
|
||||
k_strcpy(g_bootfs_state.bootloader_name, "Limine");
|
||||
k_strcpy(g_bootfs_state.bootloader_version, "6.0.0");
|
||||
|
||||
|
||||
g_bootfs_state.limine_conf[0] = '\0';
|
||||
g_bootfs_state.limine_conf_len = 0;
|
||||
|
||||
g_bootfs_state.kernel_size = 0;
|
||||
g_bootfs_state.initrd_size = 0;
|
||||
g_bootfs_state.boot_time_ms = 0;
|
||||
}
|
||||
|
||||
void bootfs_init(void) {
|
||||
bootfs_state_init();
|
||||
}
|
||||
|
||||
void bootfs_refresh_from_disk(void) {
|
||||
extern vfs_file_t* vfs_open(const char *path, const char *mode);
|
||||
extern int vfs_read(vfs_file_t *file, void *buf, int size);
|
||||
extern void vfs_close(vfs_file_t *file);
|
||||
|
||||
vfs_file_t *boot_conf = vfs_open("/limine.conf", "r");
|
||||
if (boot_conf) {
|
||||
int bytes_read = vfs_read(boot_conf, g_bootfs_state.limine_conf, 2047);
|
||||
if (bytes_read > 0) {
|
||||
g_bootfs_state.limine_conf[bytes_read] = '\0';
|
||||
g_bootfs_state.limine_conf_len = bytes_read;
|
||||
serial_write("[BOOTFS] Loaded limine.conf from partition: ");
|
||||
extern void serial_write_hex(uint64_t value);
|
||||
serial_write_hex(bytes_read);
|
||||
serial_write(" bytes\n");
|
||||
}
|
||||
vfs_close(boot_conf);
|
||||
} else {
|
||||
serial_write("[BOOTFS] Warning: /limine.conf not found on partition\n");
|
||||
}
|
||||
}
|
||||
13
src/fs/bootfs.h
Normal file
13
src/fs/bootfs.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 BOOTFS_H
|
||||
#define BOOTFS_H
|
||||
|
||||
#include "vfs.h"
|
||||
|
||||
void bootfs_init(void);
|
||||
void bootfs_refresh_from_disk(void);
|
||||
vfs_fs_ops_t* bootfs_get_ops(void);
|
||||
|
||||
#endif
|
||||
1786
src/fs/fat32.c
1786
src/fs/fat32.c
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,18 @@ typedef struct {
|
||||
uint32_t file_size; // File size
|
||||
} __attribute__((packed)) FAT32_DirEntry;
|
||||
|
||||
// Long File Name Directory Entry (32 bytes)
|
||||
typedef struct {
|
||||
uint8_t order; // Sequence number (0x40 = last, | index)
|
||||
uint16_t name1[5]; // Characters 1-5 (UCS-2)
|
||||
uint8_t attr; // Always 0x0F
|
||||
uint8_t type; // Always 0x00
|
||||
uint8_t checksum; // Checksum of short name
|
||||
uint16_t name2[6]; // Characters 6-11 (UCS-2)
|
||||
uint16_t first_cluster; // Always 0x0000
|
||||
uint16_t name3[2]; // Characters 12-13 (UCS-2)
|
||||
} __attribute__((packed)) FAT32_LFNEntry;
|
||||
|
||||
// File Attributes
|
||||
#define ATTR_READ_ONLY 0x01
|
||||
#define ATTR_HIDDEN 0x02
|
||||
@@ -70,6 +82,7 @@ typedef struct {
|
||||
#define ATTR_ARCHIVE 0x20
|
||||
#define ATTR_DEVICE 0x40
|
||||
#define ATTR_RESERVED 0x80
|
||||
#define ATTR_LFN 0x0F // LFN marker (all of the above ORed)
|
||||
|
||||
// FAT32 Constants
|
||||
#define FAT32_SECTOR_SIZE 512
|
||||
@@ -88,7 +101,9 @@ typedef struct {
|
||||
bool valid; // Is this handle valid?
|
||||
uint32_t dir_sector; // Sector containing the directory entry
|
||||
uint32_t dir_offset; // Offset within that sector
|
||||
char drive; // Drive letter (A, B, ...)
|
||||
bool is_directory; // Is this a directory?
|
||||
uint8_t attributes; // File attributes
|
||||
void *volume; // Pointer to owning FAT32_Volume (or NULL for ramfs)
|
||||
} FAT32_FileHandle;
|
||||
|
||||
// Directory Entry Info (for listing)
|
||||
@@ -101,12 +116,23 @@ typedef struct {
|
||||
uint16_t write_time;
|
||||
} FAT32_FileInfo;
|
||||
|
||||
// === VFS Integration ===
|
||||
// Forward-declared VFS ops type (defined in vfs.h)
|
||||
struct vfs_fs_ops;
|
||||
|
||||
// Get VFS ops structs for registration
|
||||
struct vfs_fs_ops* fat32_get_ramfs_ops(void);
|
||||
struct vfs_fs_ops* fat32_get_realfs_ops(void);
|
||||
|
||||
// Mount a real FAT32 volume from a block device — returns fs_private for VFS
|
||||
void* fat32_mount_volume(void *disk_ptr);
|
||||
|
||||
// === Function Declarations ===
|
||||
|
||||
// Initialization
|
||||
void fat32_init(void);
|
||||
|
||||
// File Operations
|
||||
// File Operations (backward-compat wrappers — dispatch through VFS)
|
||||
FAT32_FileHandle* fat32_open(const char *path, const char *mode);
|
||||
void fat32_close(FAT32_FileHandle *handle);
|
||||
int fat32_read(FAT32_FileHandle *handle, void *buffer, int size);
|
||||
@@ -124,7 +150,10 @@ bool fat32_is_directory(const char *path);
|
||||
// Listing
|
||||
int fat32_list_directory(const char *path, FAT32_FileInfo *entries, int max_entries);
|
||||
|
||||
// Working Directory
|
||||
// Info
|
||||
int fat32_get_info(const char *path, FAT32_FileInfo *info);
|
||||
|
||||
// Working Directory (backward compat — wraps VFS path tracking)
|
||||
bool fat32_chdir(const char *path);
|
||||
void fat32_get_current_dir(char *buffer, int size);
|
||||
bool fat32_change_drive(char drive);
|
||||
|
||||
451
src/fs/procfs.c
Normal file
451
src/fs/procfs.c
Normal file
@@ -0,0 +1,451 @@
|
||||
#include "vfs.h"
|
||||
#include "../sys/process.h"
|
||||
#include "../sys/syscall.h"
|
||||
#include "../dev/disk.h"
|
||||
#include "memory_manager.h"
|
||||
#include "core/kutils.h"
|
||||
#include "core/platform.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t pid;
|
||||
char type[32];
|
||||
int offset;
|
||||
bool is_root;
|
||||
} procfs_handle_t;
|
||||
|
||||
void* procfs_open(void *fs_private, const char *path, const char *mode) {
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
procfs_handle_t *h = (procfs_handle_t*)kmalloc(sizeof(procfs_handle_t));
|
||||
k_memset(h, 0, sizeof(procfs_handle_t));
|
||||
h->offset = 0;
|
||||
|
||||
if (path[0] == '\0') {
|
||||
h->is_root = true;
|
||||
return h;
|
||||
}
|
||||
|
||||
if (path[0] >= '0' && path[0] <= '9') {
|
||||
char pid_str[16];
|
||||
int i = 0;
|
||||
while (path[i] && path[i] != '/' && i < 15) {
|
||||
pid_str[i] = path[i];
|
||||
i++;
|
||||
}
|
||||
pid_str[i] = 0;
|
||||
h->pid = k_atoi(pid_str);
|
||||
|
||||
if (path[i] == '/') {
|
||||
k_strcpy(h->type, path + i + 1);
|
||||
} else {
|
||||
h->type[0] = 0;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
h->pid = 0xFFFFFFFF;
|
||||
k_strcpy(h->type, path);
|
||||
return h;
|
||||
}
|
||||
|
||||
void procfs_close(void *fs_private, void *handle) {
|
||||
if (handle) kfree(handle);
|
||||
}
|
||||
|
||||
int procfs_read(void *fs_private, void *handle, void *buf, int size) {
|
||||
procfs_handle_t *h = (procfs_handle_t*)handle;
|
||||
if (!h) return -1;
|
||||
|
||||
char *out = (char*)kmalloc(16384);
|
||||
if (!out) return -1;
|
||||
out[0] = 0;
|
||||
|
||||
if (h->pid == 0xFFFFFFFF) {
|
||||
if (k_strcmp(h->type, "version") == 0) {
|
||||
extern void get_os_info(os_info_t *info);
|
||||
os_info_t info;
|
||||
get_os_info(&info);
|
||||
k_strcpy(out, info.os_name);
|
||||
k_strcpy(out + k_strlen(out), " [");
|
||||
k_strcpy(out + k_strlen(out), info.os_codename);
|
||||
k_strcpy(out + k_strlen(out), "] Version ");
|
||||
k_strcpy(out + k_strlen(out), info.os_version);
|
||||
k_strcpy(out + k_strlen(out), "\nKernel: ");
|
||||
k_strcpy(out + k_strlen(out), info.kernel_name);
|
||||
k_strcpy(out + k_strlen(out), " ");
|
||||
k_strcpy(out + k_strlen(out), info.kernel_version);
|
||||
k_strcpy(out + k_strlen(out), "\nBuild: ");
|
||||
k_strcpy(out + k_strlen(out), info.build_date);
|
||||
k_strcpy(out + k_strlen(out), " ");
|
||||
k_strcpy(out + k_strlen(out), info.build_time);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
} else if (k_strcmp(h->type, "uptime") == 0) {
|
||||
extern uint32_t wm_get_ticks(void);
|
||||
uint32_t ticks = wm_get_ticks();
|
||||
k_itoa(ticks / 60, out);
|
||||
k_strcpy(out + k_strlen(out), " seconds\nRaw_Ticks:");
|
||||
char t_s[16]; k_itoa(ticks, t_s);
|
||||
k_strcpy(out + k_strlen(out), t_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
} else if (k_strcmp(h->type, "cpuinfo") == 0) {
|
||||
extern uint32_t smp_cpu_count(void);
|
||||
extern void platform_get_cpu_model(char *model);
|
||||
extern void platform_get_cpu_vendor(char *vendor);
|
||||
extern void platform_get_cpu_info(cpu_info_t *info);
|
||||
extern void platform_get_cpu_flags(char *flags_str);
|
||||
|
||||
char model[64];
|
||||
char vendor[16];
|
||||
char flags[1024];
|
||||
cpu_info_t info;
|
||||
|
||||
platform_get_cpu_model(model);
|
||||
platform_get_cpu_vendor(vendor);
|
||||
platform_get_cpu_info(&info);
|
||||
platform_get_cpu_flags(flags);
|
||||
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
out[0] = '\0';
|
||||
|
||||
// Output info for each processor
|
||||
for (uint32_t i = 0; i < cpu_count; i++) {
|
||||
char buf[32];
|
||||
|
||||
k_strcpy(out + k_strlen(out), "processor\t: ");
|
||||
k_itoa(i, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "vendor_id\t: ");
|
||||
k_strcpy(out + k_strlen(out), vendor);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpu family\t: ");
|
||||
k_itoa(info.family, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "model\t\t: ");
|
||||
k_itoa(info.model, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "model name\t: ");
|
||||
k_strcpy(out + k_strlen(out), model);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "stepping\t: ");
|
||||
k_itoa(info.stepping, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "microcode\t: 0x");
|
||||
char hex[16];
|
||||
int temp = info.microcode;
|
||||
int hex_pos = 0;
|
||||
for (int j = 7; j >= 0; j--) {
|
||||
int digit = (temp >> (j * 4)) & 0xF;
|
||||
hex[hex_pos++] = digit < 10 ? '0' + digit : 'a' + (digit - 10);
|
||||
}
|
||||
hex[hex_pos] = '\0';
|
||||
k_strcpy(out + k_strlen(out), hex);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cache size\t: ");
|
||||
k_itoa(info.cache_size, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), " KB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "physical id\t: 0\n");
|
||||
k_strcpy(out + k_strlen(out), "siblings\t: ");
|
||||
k_itoa(cpu_count, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "core id\t\t: ");
|
||||
k_itoa(i, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpu cores\t: ");
|
||||
k_itoa(cpu_count, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "apicid\t\t: ");
|
||||
k_itoa(i, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "initial apicid\t: ");
|
||||
k_itoa(i, buf);
|
||||
k_strcpy(out + k_strlen(out), buf);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "fpu\t\t: yes\n");
|
||||
k_strcpy(out + k_strlen(out), "fpu_exception\t: yes\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpuid level\t: 13\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "wp\t\t: yes\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "flags\t\t: ");
|
||||
k_strcpy(out + k_strlen(out), flags);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "bugs\t\t: \n");
|
||||
k_strcpy(out + k_strlen(out), "bogomips\t: 4800.00\n");
|
||||
|
||||
if (i < cpu_count - 1) {
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
}
|
||||
}
|
||||
} else if (k_strcmp(h->type, "meminfo") == 0) {
|
||||
extern MemStats memory_get_stats(void);
|
||||
MemStats stats = memory_get_stats();
|
||||
char m_s[32];
|
||||
|
||||
k_strcpy(out, "MemTotal:\t");
|
||||
k_itoa(stats.total_memory / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "MemFree:\t");
|
||||
k_itoa(stats.available_memory / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "MemAvailable:\t");
|
||||
k_itoa(stats.available_memory / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Buffers:\t0 kB\n");
|
||||
k_strcpy(out + k_strlen(out), "Cached:\t\t0 kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "MemUsed:\t");
|
||||
k_itoa(stats.used_memory / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "MemPeak:\t");
|
||||
k_itoa(stats.peak_memory_used / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "SwapTotal:\t0 kB\n");
|
||||
k_strcpy(out + k_strlen(out), "SwapFree:\t0 kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Dirty:\t\t0 kB\n");
|
||||
k_strcpy(out + k_strlen(out), "Writeback:\t0 kB\n");
|
||||
k_strcpy(out + k_strlen(out), "AnonPages:\t");
|
||||
k_itoa(stats.used_memory / 1024, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), " kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Mapped:\t\t0 kB\n");
|
||||
k_strcpy(out + k_strlen(out), "Shmem:\t\t0 kB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Blocks:\t\t");
|
||||
k_itoa(stats.allocated_blocks, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "FreeBlocks:\t");
|
||||
k_itoa(stats.free_blocks, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Fragmentation:\t");
|
||||
k_itoa(stats.fragmentation_percent, m_s);
|
||||
k_strcpy(out + k_strlen(out), m_s);
|
||||
k_strcpy(out + k_strlen(out), "%\n");
|
||||
} else if (k_strcmp(h->type, "devices") == 0) {
|
||||
extern int disk_get_count(void);
|
||||
extern Disk* disk_get_by_index(int index);
|
||||
int dcount = disk_get_count();
|
||||
out[0] = '\0';
|
||||
|
||||
k_strcpy(out, "Character devices:\n");
|
||||
k_strcpy(out + k_strlen(out), " 1 mem\n");
|
||||
k_strcpy(out + k_strlen(out), " 4 tty\n");
|
||||
k_strcpy(out + k_strlen(out), " 5 cua\n");
|
||||
k_strcpy(out + k_strlen(out), " 7 vcs\n");
|
||||
k_strcpy(out + k_strlen(out), " 8 stdin\n");
|
||||
k_strcpy(out + k_strlen(out), " 13 input\n");
|
||||
k_strcpy(out + k_strlen(out), " 14 sound\n");
|
||||
k_strcpy(out + k_strlen(out), " 29 fb\n");
|
||||
k_strcpy(out + k_strlen(out), "189 usb\n\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "Block devices:\n");
|
||||
for (int i = 0; i < dcount; i++) {
|
||||
Disk *d = disk_get_by_index(i);
|
||||
if (d && !d->is_partition) {
|
||||
k_strcpy(out + k_strlen(out), " 8 ");
|
||||
k_strcpy(out + k_strlen(out), d->devname);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
}
|
||||
}
|
||||
k_strcpy(out + k_strlen(out), " 11 sr\n");
|
||||
k_strcpy(out + k_strlen(out), "253 virtblk\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
process_t *proc = process_get_by_pid(h->pid);
|
||||
if (!proc) { kfree(out); return -1; }
|
||||
|
||||
if (k_strcmp(h->type, "name") == 0 || k_strcmp(h->type, "cmdline") == 0) {
|
||||
k_strcpy(out, proc->name);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
} else if (k_strcmp(h->type, "cwd") == 0) {
|
||||
k_strcpy(out, proc->cwd);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
} else if (k_strcmp(h->type, "status") == 0) {
|
||||
k_strcpy(out, "Name: ");
|
||||
k_strcpy(out + k_strlen(out), proc->name);
|
||||
k_strcpy(out + k_strlen(out), "\nPID: ");
|
||||
char pid_s[16]; k_itoa(proc->pid, pid_s);
|
||||
k_strcpy(out + k_strlen(out), pid_s);
|
||||
k_strcpy(out + k_strlen(out), "\nState: RUNNING\nMemory: ");
|
||||
uint64_t mem_val = proc->used_memory;
|
||||
if (h->pid == 0) {
|
||||
extern MemStats memory_get_stats(void);
|
||||
mem_val = memory_get_stats().used_memory;
|
||||
}
|
||||
char mem_s[32]; k_itoa(mem_val / 1024, mem_s);
|
||||
k_strcpy(out + k_strlen(out), mem_s);
|
||||
k_strcpy(out + k_strlen(out), " KB\nTicks: ");
|
||||
char tick_s[32]; k_itoa(proc->ticks, tick_s);
|
||||
k_strcpy(out + k_strlen(out), tick_s);
|
||||
k_strcpy(out + k_strlen(out), "\nIdle: ");
|
||||
k_strcpy(out + k_strlen(out), proc->is_idle ? "1" : "0");
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
int len = k_strlen(out);
|
||||
if (h->offset >= len) { kfree(out); return 0; }
|
||||
|
||||
int to_copy = len - h->offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
|
||||
k_memcpy(buf, out + h->offset, to_copy);
|
||||
h->offset += to_copy;
|
||||
kfree(out);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
int procfs_write(void *fs_private, void *handle, const void *buf, int size) {
|
||||
procfs_handle_t *h = (procfs_handle_t*)handle;
|
||||
if (!h || h->pid == 0xFFFFFFFF) return -1;
|
||||
|
||||
if (k_strcmp(h->type, "signal") == 0) {
|
||||
char cmd[16];
|
||||
int to_copy = size < 15 ? size : 15;
|
||||
k_memcpy(cmd, buf, to_copy);
|
||||
cmd[to_copy] = 0;
|
||||
|
||||
if (k_strcmp(cmd, "9") == 0 || k_strcmp(cmd, "kill") == 0) {
|
||||
process_t *proc = process_get_by_pid(h->pid);
|
||||
if (proc && proc->pid != 0) {
|
||||
process_terminate(proc);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int procfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, int max) {
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
if (path[0] == '\0') {
|
||||
int out = 0;
|
||||
k_strcpy(entries[out++].name, "version");
|
||||
entries[out-1].is_directory = 0;
|
||||
k_strcpy(entries[out++].name, "uptime");
|
||||
entries[out-1].is_directory = 0;
|
||||
k_strcpy(entries[out++].name, "cpuinfo");
|
||||
entries[out-1].is_directory = 0;
|
||||
k_strcpy(entries[out++].name, "meminfo");
|
||||
entries[out-1].is_directory = 0;
|
||||
k_strcpy(entries[out++].name, "devices");
|
||||
entries[out-1].is_directory = 0;
|
||||
|
||||
extern process_t processes[];
|
||||
for (int i = 0; i < 16 && out < max; i++) {
|
||||
if (processes[i].pid != 0xFFFFFFFF) {
|
||||
k_itoa(processes[i].pid, entries[out].name);
|
||||
entries[out].is_directory = 1;
|
||||
entries[out].size = 0;
|
||||
out++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (path[0] >= '0' && path[0] <= '9') {
|
||||
int out = 0;
|
||||
k_strcpy(entries[out++].name, "name");
|
||||
k_strcpy(entries[out++].name, "status");
|
||||
k_strcpy(entries[out++].name, "cmdline");
|
||||
k_strcpy(entries[out++].name, "cwd");
|
||||
k_strcpy(entries[out++].name, "signal");
|
||||
for(int i=0; i<out; i++) entries[i].is_directory = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool procfs_exists(void *fs_private, const char *path) {
|
||||
if (path[0] == '/') path++;
|
||||
if (path[0] == '\0') return true;
|
||||
|
||||
if (path[0] >= '0' && path[0] <= '9') {
|
||||
char pid_str[16];
|
||||
int i = 0;
|
||||
while (path[i] && path[i] != '/' && i < 15) {
|
||||
pid_str[i] = path[i];
|
||||
i++;
|
||||
}
|
||||
pid_str[i] = 0;
|
||||
uint32_t pid = k_atoi(pid_str);
|
||||
if (process_get_by_pid(pid)) return true;
|
||||
}
|
||||
|
||||
if (k_strcmp(path, "version") == 0 || k_strcmp(path, "uptime") == 0) return true;
|
||||
if (k_strcmp(path, "cpuinfo") == 0 || k_strcmp(path, "meminfo") == 0) return true;
|
||||
if (k_strcmp(path, "devices") == 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool procfs_is_dir(void *fs_private, const char *path) {
|
||||
if (path[0] == '/') path++;
|
||||
if (path[0] == '\0') return true;
|
||||
|
||||
if (path[0] >= '0' && path[0] <= '9') {
|
||||
int i = 0;
|
||||
while (path[i] && path[i] != '/') i++;
|
||||
if (path[i] == '\0') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
vfs_fs_ops_t procfs_ops = {
|
||||
.open = procfs_open,
|
||||
.close = procfs_close,
|
||||
.read = procfs_read,
|
||||
.write = procfs_write,
|
||||
.readdir = procfs_readdir,
|
||||
.exists = procfs_exists,
|
||||
.is_dir = procfs_is_dir
|
||||
};
|
||||
|
||||
vfs_fs_ops_t* procfs_get_ops(void) {
|
||||
return &procfs_ops;
|
||||
}
|
||||
8
src/fs/procfs.h
Normal file
8
src/fs/procfs.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef PROCFS_H
|
||||
#define PROCFS_H
|
||||
|
||||
#include "vfs.h"
|
||||
|
||||
vfs_fs_ops_t* procfs_get_ops(void);
|
||||
|
||||
#endif
|
||||
181
src/fs/sysfs.c
Normal file
181
src/fs/sysfs.c
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "vfs.h"
|
||||
#include "../sys/kernel_subsystem.h"
|
||||
#include "memory_manager.h"
|
||||
#include "core/kutils.h"
|
||||
|
||||
typedef struct {
|
||||
kernel_subsystem_t *sub;
|
||||
subsystem_file_t *file;
|
||||
int offset;
|
||||
} sysfs_handle_t;
|
||||
|
||||
static void* sysfs_open(void *fs_private, const char *path, const char *mode) {
|
||||
if (path[0] == '/') path++;
|
||||
if (path[0] == '\0') return NULL;
|
||||
|
||||
kernel_subsystem_t *sub = NULL;
|
||||
int last_slash = -1;
|
||||
for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j;
|
||||
|
||||
if (last_slash != -1) {
|
||||
char prefix[64];
|
||||
k_memcpy(prefix, path, last_slash);
|
||||
prefix[last_slash] = 0;
|
||||
sub = subsystem_get_by_name(prefix);
|
||||
|
||||
if (sub) {
|
||||
const char *filename = path + last_slash + 1;
|
||||
for (int j = 0; j < sub->file_count; j++) {
|
||||
if (k_strcmp(sub->files[j].name, filename) == 0) {
|
||||
sysfs_handle_t *h = (sysfs_handle_t*)kmalloc(sizeof(sysfs_handle_t));
|
||||
h->sub = sub;
|
||||
h->file = &sub->files[j];
|
||||
h->offset = 0;
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void sysfs_close(void *fs_private, void *handle) {
|
||||
if (handle) kfree(handle);
|
||||
}
|
||||
|
||||
static int sysfs_read(void *fs_private, void *handle, void *buf, int size) {
|
||||
sysfs_handle_t *h = (sysfs_handle_t*)handle;
|
||||
if (!h || !h->file || !h->file->read) return -1;
|
||||
|
||||
int bytes = h->file->read((char*)buf, size, h->offset);
|
||||
if (bytes > 0) h->offset += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int sysfs_write(void *fs_private, void *handle, const void *buf, int size) {
|
||||
sysfs_handle_t *h = (sysfs_handle_t*)handle;
|
||||
if (!h || !h->file || !h->file->write) return -1;
|
||||
|
||||
int bytes = h->file->write((const char*)buf, size, h->offset);
|
||||
if (bytes > 0) h->offset += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int sysfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, int max) {
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
kernel_subsystem_t *exact_sub = subsystem_get_by_name(path);
|
||||
int out = 0;
|
||||
|
||||
if (exact_sub) {
|
||||
for (int i = 0; i < exact_sub->file_count && out < max; i++) {
|
||||
k_strcpy(entries[out].name, exact_sub->files[i].name);
|
||||
entries[out].is_directory = 0;
|
||||
entries[out].size = 0;
|
||||
out++;
|
||||
}
|
||||
}
|
||||
|
||||
int count = subsystem_get_count();
|
||||
int path_len = k_strlen(path);
|
||||
|
||||
for (int i = 0; i < count && out < max; i++) {
|
||||
kernel_subsystem_t *s = subsystem_get_by_index(i);
|
||||
if (path_len == 0 || (k_strlen(s->name) > path_len && k_strncmp(s->name, path, path_len) == 0 && s->name[path_len] == '/')) {
|
||||
const char *sub_path = s->name + (path_len ? path_len + 1 : 0);
|
||||
char comp[64];
|
||||
int j = 0;
|
||||
while (sub_path[j] && sub_path[j] != '/' && j < 63) {
|
||||
comp[j] = sub_path[j];
|
||||
j++;
|
||||
}
|
||||
comp[j] = 0;
|
||||
|
||||
if (comp[0] == '\0') continue;
|
||||
|
||||
bool found = false;
|
||||
for (int k = 0; k < out; k++) {
|
||||
if (k_strcmp(entries[k].name, comp) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
k_strcpy(entries[out].name, comp);
|
||||
entries[out].is_directory = 1;
|
||||
entries[out].size = 0;
|
||||
out++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool sysfs_exists(void *fs_private, const char *path) {
|
||||
if (path[0] == '/') path++;
|
||||
if (path[0] == '\0') return true;
|
||||
|
||||
if (subsystem_get_by_name(path)) return true;
|
||||
|
||||
// File check
|
||||
int last_slash = -1;
|
||||
for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j;
|
||||
if (last_slash != -1) {
|
||||
char prefix[64];
|
||||
k_memcpy(prefix, path, last_slash);
|
||||
prefix[last_slash] = 0;
|
||||
kernel_subsystem_t *sub = subsystem_get_by_name(prefix);
|
||||
if (sub) {
|
||||
const char *filename = path + last_slash + 1;
|
||||
for (int j = 0; j < sub->file_count; j++) {
|
||||
if (k_strcmp(sub->files[j].name, filename) == 0) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count = subsystem_get_count();
|
||||
int path_len = k_strlen(path);
|
||||
for (int i = 0; i < count; i++) {
|
||||
kernel_subsystem_t *s = subsystem_get_by_index(i);
|
||||
if (k_strlen(s->name) > path_len && k_strncmp(s->name, path, path_len) == 0 && s->name[path_len] == '/') return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sysfs_is_dir(void *fs_private, const char *path) {
|
||||
if (path[0] == '/') path++;
|
||||
if (path[0] == '\0') return true;
|
||||
|
||||
int last_slash = -1;
|
||||
for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j;
|
||||
if (last_slash != -1) {
|
||||
char prefix[64];
|
||||
k_memcpy(prefix, path, last_slash);
|
||||
prefix[last_slash] = 0;
|
||||
kernel_subsystem_t *sub = subsystem_get_by_name(prefix);
|
||||
if (sub) {
|
||||
const char *filename = path + last_slash + 1;
|
||||
for (int j = 0; j < sub->file_count; j++) {
|
||||
if (k_strcmp(sub->files[j].name, filename) == 0) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sysfs_exists(fs_private, path);
|
||||
}
|
||||
|
||||
vfs_fs_ops_t sysfs_ops = {
|
||||
.open = sysfs_open,
|
||||
.close = sysfs_close,
|
||||
.read = sysfs_read,
|
||||
.write = sysfs_write,
|
||||
.readdir = sysfs_readdir,
|
||||
.exists = sysfs_exists,
|
||||
.is_dir = sysfs_is_dir
|
||||
};
|
||||
|
||||
vfs_fs_ops_t* sysfs_get_ops(void) {
|
||||
return &sysfs_ops;
|
||||
}
|
||||
8
src/fs/sysfs.h
Normal file
8
src/fs/sysfs.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef SYSFS_H
|
||||
#define SYSFS_H
|
||||
|
||||
#include "vfs.h"
|
||||
|
||||
vfs_fs_ops_t* sysfs_get_ops(void);
|
||||
|
||||
#endif
|
||||
816
src/fs/vfs.c
Normal file
816
src/fs/vfs.c
Normal file
@@ -0,0 +1,816 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "vfs.h"
|
||||
#include "memory_manager.h"
|
||||
#include "spinlock.h"
|
||||
#include <stddef.h>
|
||||
#include "disk.h"
|
||||
#include "process.h"
|
||||
|
||||
|
||||
static vfs_mount_t mounts[VFS_MAX_MOUNTS];
|
||||
static int mount_count = 0;
|
||||
static vfs_file_t open_files[VFS_MAX_OPEN_FILES];
|
||||
static spinlock_t vfs_lock = SPINLOCK_INIT;
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint64_t num);
|
||||
|
||||
static int vfs_strlen(const char *s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void vfs_strcpy(char *d, const char *s) {
|
||||
while ((*d++ = *s++));
|
||||
}
|
||||
|
||||
static int vfs_strcmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
static int vfs_strncmp(const char *a, const char *b, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (a[i] != b[i]) return (unsigned char)a[i] - (unsigned char)b[i];
|
||||
if (!a[i]) return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vfs_starts_with(const char *str, const char *prefix) {
|
||||
while (*prefix) {
|
||||
if (*str++ != *prefix++) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfs_path_is_parent(const char *parent, const char *child) {
|
||||
int plen = vfs_strlen(parent);
|
||||
if (vfs_strncmp(parent, child, plen) != 0) return false;
|
||||
if (child[plen] == '\0') return true;
|
||||
if (child[plen] == '/') return true;
|
||||
if (plen == 1 && parent[0] == '/') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void vfs_normalize_path(const char *cwd, const char *path, char *normalized) {
|
||||
char parts[32][64]; // Reduced size to save stack, 64 is enough for most names
|
||||
int depth = 0;
|
||||
int i = 0;
|
||||
|
||||
// Handle relative path by starting with CWD
|
||||
if (path[0] != '/' && cwd) {
|
||||
int ci = 0;
|
||||
if (cwd[0] == '/') ci = 1;
|
||||
while (cwd[ci]) {
|
||||
if (cwd[ci] == '/') { ci++; continue; }
|
||||
int j = 0;
|
||||
while (cwd[ci] && cwd[ci] != '/' && j < 63) {
|
||||
parts[depth][j++] = cwd[ci++];
|
||||
}
|
||||
parts[depth][j] = 0;
|
||||
if (j > 0) depth++;
|
||||
if (depth >= 32) break;
|
||||
if (cwd[ci] == '/') ci++;
|
||||
}
|
||||
}
|
||||
|
||||
if (path[0] == '/') i = 1;
|
||||
|
||||
while (path[i]) {
|
||||
if (path[i] == '/') { i++; continue; }
|
||||
|
||||
int j = 0;
|
||||
while (path[i] && path[i] != '/' && j < 63) {
|
||||
parts[depth][j++] = path[i++];
|
||||
}
|
||||
parts[depth][j] = 0;
|
||||
|
||||
if (parts[depth][0] == '.' && parts[depth][1] == 0) {
|
||||
// "." skip
|
||||
} else if (parts[depth][0] == '.' && parts[depth][1] == '.' && parts[depth][2] == 0) {
|
||||
// ".." pop
|
||||
if (depth > 0) depth--;
|
||||
} else {
|
||||
if (j > 0) {
|
||||
depth++;
|
||||
if (depth >= 32) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path[i] == '/') i++;
|
||||
}
|
||||
|
||||
normalized[0] = '/';
|
||||
int pos = 1;
|
||||
for (int k = 0; k < depth; k++) {
|
||||
int l = 0;
|
||||
while (parts[k][l] && pos < VFS_MAX_PATH - 2) {
|
||||
normalized[pos++] = parts[k][l++];
|
||||
}
|
||||
if (k < depth - 1) normalized[pos++] = '/';
|
||||
}
|
||||
normalized[pos] = 0;
|
||||
|
||||
if (pos == 1 && normalized[0] == '/') {
|
||||
normalized[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static vfs_mount_t* vfs_resolve_mount(const char *path, const char **rel_path_out) {
|
||||
vfs_mount_t *best = NULL;
|
||||
int best_len = -1;
|
||||
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (!mounts[i].active) continue;
|
||||
|
||||
int mlen = mounts[i].path_len;
|
||||
|
||||
if (mlen == 1 && mounts[i].path[0] == '/') {
|
||||
if (best_len < 1) {
|
||||
best = &mounts[i];
|
||||
best_len = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vfs_strncmp(path, mounts[i].path, mlen) == 0) {
|
||||
if (path[mlen] == '/' || path[mlen] == '\0') {
|
||||
if (mlen > best_len) {
|
||||
best = &mounts[i];
|
||||
best_len = mlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best && rel_path_out) {
|
||||
const char *rel = path + best_len;
|
||||
while (*rel == '/') rel++;
|
||||
*rel_path_out = rel;
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
static vfs_file_t* vfs_alloc_file(void) {
|
||||
for (int i = 0; i < VFS_MAX_OPEN_FILES; i++) {
|
||||
if (!open_files[i].valid) {
|
||||
open_files[i].valid = true;
|
||||
open_files[i].fs_handle = NULL;
|
||||
open_files[i].mount = NULL;
|
||||
open_files[i].position = 0;
|
||||
open_files[i].is_device = false;
|
||||
return &open_files[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void vfs_free_file(vfs_file_t *f) {
|
||||
if (f) {
|
||||
f->valid = false;
|
||||
f->fs_handle = NULL;
|
||||
f->mount = NULL;
|
||||
f->position = 0;
|
||||
f->is_device = false;
|
||||
}
|
||||
}
|
||||
|
||||
void vfs_init(void) {
|
||||
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||
mounts[i].active = false;
|
||||
}
|
||||
for (int i = 0; i < VFS_MAX_OPEN_FILES; i++) {
|
||||
open_files[i].valid = false;
|
||||
}
|
||||
mount_count = 0;
|
||||
|
||||
serial_write("[VFS] Ready\n");
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Mount / Unmount
|
||||
// ===============
|
||||
|
||||
bool vfs_mount(const char *mount_path, const char *device, const char *fs_type,
|
||||
vfs_fs_ops_t *ops, void *fs_private) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
|
||||
if (mount_count >= VFS_MAX_MOUNTS) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
serial_write("[VFS] ERROR: Mount table full\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (mounts[i].active && vfs_strcmp(mounts[i].path, mount_path) == 0) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
serial_write("[VFS] ERROR: Mount point already in use: ");
|
||||
serial_write(mount_path);
|
||||
serial_write("\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_mount_t *m = &mounts[mount_count];
|
||||
vfs_strcpy(m->path, mount_path);
|
||||
m->path_len = vfs_strlen(mount_path);
|
||||
m->ops = ops;
|
||||
m->fs_private = fs_private;
|
||||
vfs_strcpy(m->device, device ? device : "none");
|
||||
vfs_strcpy(m->fs_type, fs_type ? fs_type : "unknown");
|
||||
m->active = true;
|
||||
mount_count++;
|
||||
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
|
||||
serial_write("[VFS] Mounted ");
|
||||
serial_write(fs_type);
|
||||
serial_write(" (");
|
||||
serial_write(device ? device : "none");
|
||||
serial_write(") at ");
|
||||
serial_write(mount_path);
|
||||
serial_write("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfs_umount(const char *mount_path) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (mounts[i].active && vfs_strcmp(mounts[i].path, mount_path) == 0) {
|
||||
for (int j = 0; j < VFS_MAX_OPEN_FILES; j++) {
|
||||
if (open_files[j].valid && open_files[j].mount == &mounts[i]) {
|
||||
if (mounts[i].ops->close) {
|
||||
mounts[i].ops->close(mounts[i].fs_private, open_files[j].fs_handle);
|
||||
}
|
||||
vfs_free_file(&open_files[j]);
|
||||
}
|
||||
}
|
||||
|
||||
serial_write("[VFS] Unmounted ");
|
||||
serial_write(mounts[i].path);
|
||||
serial_write("\n");
|
||||
|
||||
mounts[i].active = false;
|
||||
|
||||
// Compact array
|
||||
for (int k = i; k < mount_count - 1; k++) {
|
||||
mounts[k] = mounts[k + 1];
|
||||
}
|
||||
mount_count--;
|
||||
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==============
|
||||
// File Operations
|
||||
// ==============
|
||||
|
||||
vfs_file_t* vfs_open(const char *path, const char *mode) {
|
||||
if (!path || !mode) return NULL;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
process_t *proc = process_get_current();
|
||||
vfs_normalize_path(proc ? proc->cwd : "/", path, normalized);
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
|
||||
// Fallback for block devices (/dev/sda etc)
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
const char *devname = normalized + 5;
|
||||
Disk *d = disk_get_by_name(devname);
|
||||
if (d && (!mount || mount->path_len == 1)) {
|
||||
vfs_file_t *vf = vfs_alloc_file();
|
||||
if (vf) {
|
||||
vf->mount = &mounts[0];
|
||||
vf->fs_handle = (void*)d;
|
||||
vf->is_device = true;
|
||||
vf->position = 0;
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
return vf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mount || !mount->ops->open) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!rel_path || rel_path[0] == '\0') {
|
||||
rel_path = "/";
|
||||
}
|
||||
|
||||
vfs_file_t *vf = vfs_alloc_file();
|
||||
if (!vf) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
serial_write("[VFS] ERROR: No free file handles\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vf->mount = mount;
|
||||
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
|
||||
void *fs_handle = mount->ops->open(mount->fs_private, rel_path, mode);
|
||||
if (!fs_handle) {
|
||||
flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
vfs_free_file(vf);
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vf->fs_handle = fs_handle;
|
||||
return vf;
|
||||
}
|
||||
|
||||
void vfs_close(vfs_file_t *file) {
|
||||
if (!file || !file->valid) return;
|
||||
|
||||
vfs_mount_t *mount = file->mount;
|
||||
if (mount && mount->ops->close) {
|
||||
mount->ops->close(mount->fs_private, file->fs_handle);
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
vfs_free_file(file);
|
||||
spinlock_release_irqrestore(&vfs_lock, flags);
|
||||
}
|
||||
|
||||
int vfs_read(vfs_file_t *file, void *buf, int size) {
|
||||
if (!file || !file->valid || !file->mount) return -1;
|
||||
|
||||
if (file->is_device) {
|
||||
Disk *d = (Disk*)file->fs_handle;
|
||||
if (!d) return -1;
|
||||
|
||||
uint32_t total_read = 0;
|
||||
uint32_t sector = (uint32_t)(file->position / 512);
|
||||
uint32_t offset = (uint32_t)(file->position % 512);
|
||||
uint8_t sector_buf[512];
|
||||
|
||||
while (total_read < (uint32_t)size) {
|
||||
if (sector >= d->total_sectors) break;
|
||||
if (d->read_sector(d, sector, sector_buf) != 0) break;
|
||||
|
||||
uint32_t to_copy = 512 - offset;
|
||||
if (to_copy > (uint32_t)size - total_read) to_copy = (uint32_t)size - total_read;
|
||||
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy((uint8_t*)buf + total_read, sector_buf + offset, to_copy);
|
||||
|
||||
total_read += to_copy;
|
||||
file->position += to_copy;
|
||||
sector++;
|
||||
offset = 0;
|
||||
}
|
||||
return (int)total_read;
|
||||
}
|
||||
|
||||
if (!file->mount->ops->read) return -1;
|
||||
int ret = file->mount->ops->read(file->mount->fs_private, file->fs_handle, buf, size);
|
||||
if (ret > 0) file->position += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int vfs_write(vfs_file_t *file, const void *buf, int size) {
|
||||
if (!file || !file->valid || !file->mount) return -1;
|
||||
if (!file->mount->ops->write) return -1;
|
||||
|
||||
return file->mount->ops->write(file->mount->fs_private, file->fs_handle, buf, size);
|
||||
}
|
||||
|
||||
int vfs_seek(vfs_file_t *file, int offset, int whence) {
|
||||
if (!file || !file->valid || !file->mount) return -1;
|
||||
|
||||
if (file->is_device) {
|
||||
Disk *d = (Disk*)file->fs_handle;
|
||||
if (!d) return -1;
|
||||
uint64_t new_pos = file->position;
|
||||
if (whence == 0) new_pos = (uint64_t)offset; // SET
|
||||
else if (whence == 1) new_pos += (uint64_t)offset; // CUR
|
||||
else if (whence == 2) new_pos = (uint64_t)(d->total_sectors * 512 + offset); // END
|
||||
|
||||
if (new_pos > (uint64_t)d->total_sectors * 512) new_pos = (uint64_t)d->total_sectors * 512;
|
||||
file->position = new_pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!file->mount->ops->seek) return -1;
|
||||
int ret = file->mount->ops->seek(file->mount->fs_private, file->fs_handle, offset, whence);
|
||||
if (ret == 0) {
|
||||
// Sync position back from driver if possible
|
||||
if (file->mount->ops->get_position) {
|
||||
file->position = file->mount->ops->get_position(file->fs_handle);
|
||||
} else {
|
||||
// Manual sync if driver doesn't support get_position but seek succeeded
|
||||
if (whence == 0) file->position = offset;
|
||||
else if (whence == 1) file->position += offset;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t vfs_file_position(vfs_file_t *file) {
|
||||
if (!file || !file->valid || !file->mount) return 0;
|
||||
if (file->is_device) return (uint32_t)file->position;
|
||||
if (!file->mount->ops->get_position) return 0;
|
||||
return file->mount->ops->get_position(file->fs_handle);
|
||||
}
|
||||
|
||||
uint32_t vfs_file_size(vfs_file_t *file) {
|
||||
if (!file || !file->valid || !file->mount) return 0;
|
||||
if (file->is_device) {
|
||||
Disk *d = (Disk*)file->fs_handle;
|
||||
return d ? d->total_sectors * 512 : 0;
|
||||
}
|
||||
if (!file->mount->ops->get_size) return 0;
|
||||
return file->mount->ops->get_size(file->fs_handle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int vfs_list_directory(const char *path, vfs_dirent_t *entries, int max) {
|
||||
if (!path || !entries) return -1;
|
||||
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
|
||||
int count = 0;
|
||||
if (mount && mount->ops->readdir) {
|
||||
if (!rel_path || rel_path[0] == '\0') rel_path = "/";
|
||||
count = mount->ops->readdir(mount->fs_private, rel_path, entries, max);
|
||||
if (count < 0) count = 0;
|
||||
}
|
||||
|
||||
uint64_t v_flags = spinlock_acquire_irqsave(&vfs_lock);
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (!mounts[i].active) continue;
|
||||
if (vfs_strcmp(mounts[i].path, normalized) == 0) continue;
|
||||
|
||||
if (vfs_path_is_parent(normalized, mounts[i].path)) {
|
||||
const char *sub = mounts[i].path + vfs_strlen(normalized);
|
||||
if (*sub == '/') sub++;
|
||||
|
||||
if (*sub != '\0') {
|
||||
char comp[VFS_MAX_NAME];
|
||||
int j = 0;
|
||||
while (sub[j] && sub[j] != '/' && j < VFS_MAX_NAME - 1) {
|
||||
comp[j] = sub[j];
|
||||
j++;
|
||||
}
|
||||
comp[j] = 0;
|
||||
|
||||
bool found = false;
|
||||
for (int k = 0; k < count; k++) {
|
||||
if (vfs_strcmp(entries[k].name, comp) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && count < max) {
|
||||
vfs_strcpy(entries[count].name, comp);
|
||||
entries[count].is_directory = 1;
|
||||
entries[count].size = 0;
|
||||
entries[count].start_cluster = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spinlock_release_irqrestore(&vfs_lock, v_flags);
|
||||
|
||||
// Special case: Ensure "dev", "sys", "proc" are visible in "/"
|
||||
if (vfs_strcmp(normalized, "/") == 0) {
|
||||
const char *virtual_dirs[] = {"dev", "sys", "proc"};
|
||||
for (int v = 0; v < 3; v++) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (vfs_strcmp(entries[i].name, virtual_dirs[v]) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && count < max) {
|
||||
vfs_strcpy(entries[count].name, virtual_dirs[v]);
|
||||
entries[count].is_directory = 1;
|
||||
entries[count].size = 0;
|
||||
entries[count].start_cluster = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: /dev listing for block devices
|
||||
if (vfs_strcmp(normalized, "/dev") == 0) {
|
||||
int dcount = disk_get_count();
|
||||
for (int i = 0; i < dcount && count < max; i++) {
|
||||
Disk *d = disk_get_by_index(i);
|
||||
if (d) {
|
||||
bool found = false;
|
||||
for (int k = 0; k < count; k++) {
|
||||
if (vfs_strcmp(entries[k].name, d->devname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
vfs_strcpy(entries[count].name, d->devname);
|
||||
entries[count].size = d->total_sectors * 512;
|
||||
entries[count].is_directory = 0;
|
||||
entries[count].start_cluster = 0;
|
||||
entries[count].write_date = 0;
|
||||
entries[count].write_time = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool vfs_mkdir(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
if (!mount || !rel_path || rel_path[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mount || !mount->ops->mkdir) return false;
|
||||
return mount->ops->mkdir(mount->fs_private, rel_path);
|
||||
}
|
||||
|
||||
bool vfs_rmdir(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
if (normalized[0] == '/' && normalized[1] == '\0') return false;
|
||||
if (vfs_strcmp(normalized, "/dev") == 0) return false;
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
if (!mount || !rel_path || rel_path[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mount || !mount->ops->rmdir) return false;
|
||||
return mount->ops->rmdir(mount->fs_private, rel_path);
|
||||
}
|
||||
|
||||
bool vfs_delete(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
if (normalized[0] == '/' && normalized[1] == '\0') return false;
|
||||
if (vfs_strcmp(normalized, "/dev") == 0) return false;
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
if (!mount || !rel_path || rel_path[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mount || !mount->ops->unlink) return false;
|
||||
return mount->ops->unlink(mount->fs_private, rel_path);
|
||||
}
|
||||
|
||||
bool vfs_rename(const char *old_path, const char *new_path) {
|
||||
if (!old_path || !new_path) return false;
|
||||
|
||||
char norm_old[VFS_MAX_PATH], norm_new[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", old_path, norm_old);
|
||||
vfs_normalize_path("/", new_path, norm_new);
|
||||
|
||||
const char *rel_old = NULL, *rel_new = NULL;
|
||||
vfs_mount_t *mount_old = vfs_resolve_mount(norm_old, &rel_old);
|
||||
vfs_mount_t *mount_new = vfs_resolve_mount(norm_new, &rel_new);
|
||||
|
||||
if (!mount_old || mount_old != mount_new) return false;
|
||||
if (!mount_old->ops->rename) return false;
|
||||
|
||||
if (!rel_old || rel_old[0] == '\0') return false;
|
||||
if (!rel_new || rel_new[0] == '\0') return false;
|
||||
|
||||
return mount_old->ops->rename(mount_old->fs_private, rel_old, rel_new);
|
||||
}
|
||||
|
||||
bool vfs_exists(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
if (normalized[0] == '/' && normalized[1] == '\0') return true;
|
||||
|
||||
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (mounts[i].active && vfs_starts_with(mounts[i].path, normalized)) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
|
||||
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
||||
vfs_strcmp(normalized, "/sys") == 0 ||
|
||||
vfs_strcmp(normalized, "/proc") == 0) return true;
|
||||
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
const char *dev = normalized + 5;
|
||||
if (disk_get_by_name(dev)) return true;
|
||||
}
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
if (!mount || !mount->ops->exists) return false;
|
||||
|
||||
if (!rel_path || rel_path[0] == '\0') return true;
|
||||
|
||||
return mount->ops->exists(mount->fs_private, rel_path);
|
||||
}
|
||||
|
||||
bool vfs_is_directory(const char *path) {
|
||||
if (!path) return false;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
if (normalized[0] == '/' && normalized[1] == '\0') return true;
|
||||
|
||||
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (mounts[i].active && vfs_path_is_parent(normalized, mounts[i].path)) {
|
||||
if (vfs_strcmp(mounts[i].path, normalized) == 0) {
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
return true;
|
||||
}
|
||||
// If normalized is a parent of a mount, it's a virtual directory
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
|
||||
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
||||
vfs_strcmp(normalized, "/sys") == 0 ||
|
||||
vfs_strcmp(normalized, "/proc") == 0) return true;
|
||||
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
const char *dev = normalized + 5;
|
||||
Disk *d = disk_get_by_name(dev);
|
||||
if (d) return false;
|
||||
}
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
if (!mount) return false;
|
||||
|
||||
if (!rel_path || rel_path[0] == '\0') return true;
|
||||
|
||||
if (!mount->ops->is_dir) return false;
|
||||
return mount->ops->is_dir(mount->fs_private, rel_path);
|
||||
}
|
||||
|
||||
int vfs_get_info(const char *path, vfs_dirent_t *info) {
|
||||
if (!path || !info) return -1;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path("/", path, normalized);
|
||||
|
||||
if (normalized[0] == '/' && normalized[1] == '\0') {
|
||||
vfs_strcpy(info->name, "/");
|
||||
info->size = 0;
|
||||
info->is_directory = 1;
|
||||
info->start_cluster = 0;
|
||||
info->write_date = 0;
|
||||
info->write_time = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
||||
vfs_strcmp(normalized, "/sys") == 0 ||
|
||||
vfs_strcmp(normalized, "/proc") == 0) {
|
||||
const char *name = normalized + 1;
|
||||
vfs_strcpy(info->name, name);
|
||||
info->size = 0;
|
||||
info->is_directory = 1;
|
||||
info->start_cluster = 0;
|
||||
info->write_date = 0;
|
||||
info->write_time = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
||||
for (int i = 0; i < mount_count; i++) {
|
||||
if (mounts[i].active && vfs_path_is_parent(normalized, mounts[i].path)) {
|
||||
if (vfs_strcmp(mounts[i].path, normalized) != 0) {
|
||||
const char *p = normalized + vfs_strlen(normalized);
|
||||
while (p > normalized && *(p-1) != '/') p--;
|
||||
vfs_strcpy(info->name, p);
|
||||
info->size = 0;
|
||||
info->is_directory = 1;
|
||||
info->start_cluster = 0;
|
||||
info->write_date = 0;
|
||||
info->write_time = 0;
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
||||
|
||||
// Device check
|
||||
if (vfs_starts_with(normalized, "/dev/")) {
|
||||
const char *dev = normalized + 5;
|
||||
Disk *d = disk_get_by_name(dev);
|
||||
if (d) {
|
||||
vfs_strcpy(info->name, d->devname);
|
||||
info->size = d->total_sectors * 512;
|
||||
info->is_directory = 0;
|
||||
info->start_cluster = 0;
|
||||
info->write_date = 0;
|
||||
info->write_time = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char *rel_path = NULL;
|
||||
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
||||
if (!mount || !mount->ops->get_info) return -1;
|
||||
|
||||
if (!rel_path || rel_path[0] == '\0') {
|
||||
// Info about mount root
|
||||
vfs_strcpy(info->name, mount->device);
|
||||
info->size = 0;
|
||||
info->is_directory = 1;
|
||||
info->start_cluster = 0;
|
||||
info->write_date = 0;
|
||||
info->write_time = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mount->ops->get_info(mount->fs_private, rel_path, info);
|
||||
}
|
||||
|
||||
int vfs_get_mount_count(void) {
|
||||
return mount_count;
|
||||
}
|
||||
|
||||
vfs_mount_t* vfs_get_mount(int index) {
|
||||
if (index < 0 || index >= mount_count) return NULL;
|
||||
if (!mounts[index].active) return NULL;
|
||||
return &mounts[index];
|
||||
}
|
||||
|
||||
void vfs_automount_partition(const char *devname) {
|
||||
char mount_path[64] = "/mnt/";
|
||||
int i = 5;
|
||||
const char *d = devname;
|
||||
while (*d && i < 62) mount_path[i++] = *d++;
|
||||
mount_path[i] = 0;
|
||||
|
||||
serial_write("[VFS] Auto-mount requested for ");
|
||||
serial_write(devname);
|
||||
serial_write(" at ");
|
||||
serial_write(mount_path);
|
||||
serial_write("\n");
|
||||
}
|
||||
117
src/fs/vfs.h
Normal file
117
src/fs/vfs.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 VFS_H
|
||||
#define VFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define VFS_MAX_PATH 1024
|
||||
#define VFS_MAX_NAME 256
|
||||
#define VFS_MAX_MOUNTS 16
|
||||
#define VFS_MAX_OPEN_FILES 64
|
||||
|
||||
// Forward declarations
|
||||
typedef struct vfs_mount vfs_mount_t;
|
||||
typedef struct vfs_file vfs_file_t;
|
||||
|
||||
// Directory entry for readdir
|
||||
typedef struct vfs_dirent {
|
||||
char name[VFS_MAX_NAME];
|
||||
uint32_t size;
|
||||
uint8_t is_directory;
|
||||
uint32_t start_cluster;
|
||||
uint16_t write_date;
|
||||
uint16_t write_time;
|
||||
} vfs_dirent_t;
|
||||
|
||||
// Filesystem operations — implemented by each filesystem type
|
||||
typedef struct vfs_fs_ops {
|
||||
// File operations — return opaque FS handle
|
||||
void* (*open)(void *fs_private, const char *rel_path, const char *mode);
|
||||
void (*close)(void *fs_private, void *file_handle);
|
||||
int (*read)(void *fs_private, void *file_handle, void *buf, int size);
|
||||
int (*write)(void *fs_private, void *file_handle, const void *buf, int size);
|
||||
int (*seek)(void *fs_private, void *file_handle, int offset, int whence);
|
||||
|
||||
// Directory operations
|
||||
int (*readdir)(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max);
|
||||
bool (*mkdir)(void *fs_private, const char *rel_path);
|
||||
bool (*rmdir)(void *fs_private, const char *rel_path);
|
||||
bool (*unlink)(void *fs_private, const char *rel_path);
|
||||
bool (*rename)(void *fs_private, const char *old_path, const char *new_path);
|
||||
|
||||
// Query operations
|
||||
bool (*exists)(void *fs_private, const char *rel_path);
|
||||
bool (*is_dir)(void *fs_private, const char *rel_path);
|
||||
int (*get_info)(void *fs_private, const char *rel_path, vfs_dirent_t *info);
|
||||
|
||||
// Handle info (for backward compat with syscall position/size queries)
|
||||
uint32_t (*get_position)(void *file_handle);
|
||||
uint32_t (*get_size)(void *file_handle);
|
||||
} vfs_fs_ops_t;
|
||||
|
||||
// VFS file handle
|
||||
struct vfs_file {
|
||||
void *fs_handle; // FS-specific handle (e.g. FAT32_FileHandle*)
|
||||
vfs_mount_t *mount; // Mount this file belongs to
|
||||
bool valid;
|
||||
uint64_t position; // Current Seek Position (for raw devices/fallbacks)
|
||||
bool is_device; // Is this a raw device handle?
|
||||
};
|
||||
|
||||
// Mount entry
|
||||
struct vfs_mount {
|
||||
char path[256]; // Mount point (e.g. "/", "/mnt/sda1")
|
||||
int path_len;
|
||||
vfs_fs_ops_t *ops;
|
||||
void *fs_private; // FS-specific data (e.g. FAT32_Volume*)
|
||||
char device[32]; // Device name (e.g. "ramfs", "sda1")
|
||||
char fs_type[16]; // "ramfs", "fat32"
|
||||
bool active;
|
||||
};
|
||||
|
||||
// Initialization
|
||||
void vfs_init(void);
|
||||
|
||||
// Mount/unmount
|
||||
bool vfs_mount(const char *mount_path, const char *device, const char *fs_type,
|
||||
vfs_fs_ops_t *ops, void *fs_private);
|
||||
bool vfs_umount(const char *mount_path);
|
||||
|
||||
// File operations
|
||||
vfs_file_t* vfs_open(const char *path, const char *mode);
|
||||
void vfs_close(vfs_file_t *file);
|
||||
int vfs_read(vfs_file_t *file, void *buf, int size);
|
||||
int vfs_write(vfs_file_t *file, const void *buf, int size);
|
||||
int vfs_seek(vfs_file_t *file, int offset, int whence);
|
||||
|
||||
// Directory operations
|
||||
int vfs_list_directory(const char *path, vfs_dirent_t *entries, int max);
|
||||
bool vfs_mkdir(const char *path);
|
||||
bool vfs_rmdir(const char *path);
|
||||
bool vfs_delete(const char *path);
|
||||
bool vfs_rename(const char *old_path, const char *new_path);
|
||||
|
||||
// Query operations
|
||||
bool vfs_exists(const char *path);
|
||||
bool vfs_is_directory(const char *path);
|
||||
int vfs_get_info(const char *path, vfs_dirent_t *info);
|
||||
|
||||
// Mount enumeration
|
||||
int vfs_get_mount_count(void);
|
||||
vfs_mount_t* vfs_get_mount(int index);
|
||||
|
||||
// Block device auto-mount
|
||||
void vfs_automount_partition(const char *devname);
|
||||
|
||||
// Path utilities
|
||||
void vfs_normalize_path(const char *cwd, const char *path, char *normalized);
|
||||
|
||||
// Backward compat: get position/size from vfs_file
|
||||
uint32_t vfs_file_position(vfs_file_t *file);
|
||||
uint32_t vfs_file_size(vfs_file_t *file);
|
||||
|
||||
#endif
|
||||
BIN
src/images/wallpapers/bored.jpg
Normal file
BIN
src/images/wallpapers/bored.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 574 KiB |
4
src/library/bsh/boot.bsh
Normal file
4
src/library/bsh/boot.bsh
Normal file
@@ -0,0 +1,4 @@
|
||||
# BoredShell boot script
|
||||
# Runs once on boot for the first shell.
|
||||
# Example:
|
||||
# echo "Boot script executed"
|
||||
35
src/library/bsh/bshrc
Normal file
35
src/library/bsh/bshrc
Normal file
@@ -0,0 +1,35 @@
|
||||
# BoredShell rc file
|
||||
# Location: /Library/bsh/bshrc
|
||||
#
|
||||
# Format (initial): key=value
|
||||
# Lines starting with # are comments.
|
||||
#
|
||||
# PATH controls command lookup order (colon-separated).
|
||||
PATH=/bin:/root/Apps
|
||||
|
||||
# Scripts to run automatically.
|
||||
# STARTUP runs for interactive shells.
|
||||
# BOOT_SCRIPT runs once on boot (if enabled by the shell).
|
||||
STARTUP=/Library/bsh/startup.bsh
|
||||
BOOT_SCRIPT=/Library/bsh/boot.bsh
|
||||
|
||||
# Prompt templates.
|
||||
# Tokens:
|
||||
# %n = username
|
||||
# %h = hostname
|
||||
# %~ = cwd ("~" for /root)
|
||||
# %T = time (HH:MM)
|
||||
PROMPT_LEFT=%n@%h:%~$
|
||||
PROMPT_RIGHT=%T
|
||||
|
||||
# History settings.
|
||||
HISTORY_FILE=/Library/bsh/history
|
||||
HISTORY_SIZE=200
|
||||
|
||||
# Feature toggles.
|
||||
GLOB=true
|
||||
COMPLETE=true
|
||||
SUGGEST=true
|
||||
|
||||
# Example aliases (future support).
|
||||
# alias ll=ls -la
|
||||
5
src/library/bsh/startup.bsh
Normal file
5
src/library/bsh/startup.bsh
Normal file
@@ -0,0 +1,5 @@
|
||||
# BoredShell startup script
|
||||
# Runs for interactive shells.
|
||||
# Example:
|
||||
# echo "Welcome to BoredShell"
|
||||
sysfetch
|
||||
@@ -479,7 +479,7 @@ void memory_validate(void) {
|
||||
}
|
||||
|
||||
if (errors == 0) {
|
||||
cmd_write("Memory validation: OK\n");
|
||||
cmd_write("Memory validation: [OK]\n");
|
||||
} else {
|
||||
cmd_write("Memory validation failed with ");
|
||||
cmd_write_int(errors);
|
||||
|
||||
@@ -1133,7 +1133,6 @@ tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port,
|
||||
(cpcb->remote_port == port) &&
|
||||
ip_addr_cmp(&cpcb->local_ip, &pcb->local_ip) &&
|
||||
ip_addr_cmp(&cpcb->remote_ip, ipaddr)) {
|
||||
/* linux returns EISCONN here, but ERR_USE should be OK for us */
|
||||
return ERR_USE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ int network_init(void) {
|
||||
ipv4_address_t ip;
|
||||
network_get_ipv4_address(&ip);
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("[NETWORK] IP Assigned: ");
|
||||
serial_write("[NET] IP Assigned: ");
|
||||
char buf[32];
|
||||
k_itoa(ip.bytes[0], buf); serial_write(buf); serial_write(".");
|
||||
k_itoa(ip.bytes[1], buf); serial_write(buf); serial_write(".");
|
||||
@@ -96,7 +96,7 @@ int network_init(void) {
|
||||
k_itoa(ip.bytes[3], buf); serial_write(buf); serial_write("\n");
|
||||
} else {
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("[NETWORK] DHCP Failed during init\n");
|
||||
serial_write("[NET] DHCP Failed during init\n");
|
||||
}
|
||||
|
||||
// Set default DNS server (1.1.1.1)
|
||||
|
||||
24
src/sys/bootfs_state.h
Normal file
24
src/sys/bootfs_state.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||
#ifndef BOOTFS_STATE_H
|
||||
#define BOOTFS_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char bootloader_name[64];
|
||||
char bootloader_version[64];
|
||||
uint64_t boot_time_ms;
|
||||
uint8_t boot_flags;
|
||||
char limine_conf[2048];
|
||||
int limine_conf_len;
|
||||
uint32_t num_modules;
|
||||
uint32_t kernel_size;
|
||||
uint32_t initrd_size;
|
||||
|
||||
} bootfs_state_t;
|
||||
extern bootfs_state_t g_bootfs_state;
|
||||
void bootfs_state_init(void);
|
||||
|
||||
#endif
|
||||
@@ -4,15 +4,15 @@
|
||||
#ifndef CMD_H
|
||||
#define CMD_H
|
||||
|
||||
#include "wm.h"
|
||||
|
||||
extern Window win_cmd;
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void cmd_init(void);
|
||||
void cmd_reset(void);
|
||||
|
||||
// IO Functions
|
||||
void cmd_write(const char *str);
|
||||
void cmd_write_len(const char *str, size_t len);
|
||||
void cmd_putchar(char c);
|
||||
void cmd_write_int(int n);
|
||||
void cmd_write_hex(uint64_t n);
|
||||
@@ -22,10 +22,9 @@ void cmd_screen_clear(void);
|
||||
void cmd_increment_msg_count(void);
|
||||
void cmd_reset_msg_count(void);
|
||||
|
||||
void cmd_handle_resize(Window *win, int w, int h);
|
||||
void cmd_handle_click(Window *win, int x, int y);
|
||||
|
||||
uint32_t cmd_get_config_value(const char *key);
|
||||
void cmd_set_current_color(uint32_t color);
|
||||
void cmd_set_raw_mode(bool enabled);
|
||||
void cmd_process_finished(void);
|
||||
|
||||
#endif
|
||||
76
src/sys/cmd_stub.c
Normal file
76
src/sys/cmd_stub.c
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "cmd.h"
|
||||
#include "core/kutils.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
|
||||
static void serial_write_char(char c) {
|
||||
char buf[2] = { c, 0 };
|
||||
serial_write(buf);
|
||||
}
|
||||
|
||||
void cmd_init(void) {
|
||||
}
|
||||
|
||||
void cmd_reset(void) {
|
||||
}
|
||||
|
||||
void cmd_write(const char *str) {
|
||||
if (!str) return;
|
||||
serial_write(str);
|
||||
}
|
||||
|
||||
void cmd_write_len(const char *str, size_t len) {
|
||||
if (!str || len == 0) return;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
serial_write_char(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_putchar(char c) {
|
||||
serial_write_char(c);
|
||||
}
|
||||
|
||||
void cmd_write_int(int n) {
|
||||
char buf[32];
|
||||
k_itoa(n, buf);
|
||||
cmd_write(buf);
|
||||
}
|
||||
|
||||
void cmd_write_hex(uint64_t n) {
|
||||
char buf[17];
|
||||
k_itoa_hex(n, buf);
|
||||
cmd_write("0x");
|
||||
cmd_write(buf);
|
||||
}
|
||||
|
||||
int cmd_get_cursor_col(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cmd_screen_clear(void) {
|
||||
}
|
||||
|
||||
void cmd_increment_msg_count(void) {
|
||||
}
|
||||
|
||||
void cmd_reset_msg_count(void) {
|
||||
}
|
||||
|
||||
uint32_t cmd_get_config_value(const char *key) {
|
||||
(void)key;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cmd_set_current_color(uint32_t color) {
|
||||
(void)color;
|
||||
}
|
||||
|
||||
void cmd_set_raw_mode(bool enabled) {
|
||||
(void)enabled;
|
||||
}
|
||||
|
||||
void cmd_process_finished(void) {
|
||||
}
|
||||
477
src/sys/file_index.c
Normal file
477
src/sys/file_index.c
Normal file
@@ -0,0 +1,477 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "file_index.h"
|
||||
#include "vfs.h"
|
||||
#include "memory_manager.h"
|
||||
#include "spinlock.h"
|
||||
#include <stddef.h>
|
||||
|
||||
static file_index_t g_file_index = {0};
|
||||
static spinlock_t g_index_lock = SPINLOCK_INIT;
|
||||
static bool g_index_valid = false;
|
||||
|
||||
static int str_len(const char *s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void str_copy(char *d, const char *s) {
|
||||
while ((*d++ = *s++));
|
||||
}
|
||||
|
||||
static int str_cmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
static void str_cat(char *d, const char *s) {
|
||||
while (*d) d++;
|
||||
str_copy(d, s);
|
||||
}
|
||||
|
||||
static bool str_starts_with(const char *str, const char *prefix) {
|
||||
while (*prefix) {
|
||||
if (*str++ != *prefix++) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int fuzzy_match_score(const char *query, const char *filename) {
|
||||
if (!query || !filename) return 0;
|
||||
|
||||
int score = 0;
|
||||
int query_idx = 0;
|
||||
int consecutive = 0;
|
||||
|
||||
for (int i = 0; filename[i] && query_idx < 256; i++) {
|
||||
char fc = filename[i];
|
||||
char qc = query[query_idx];
|
||||
|
||||
if (fc >= 'A' && fc <= 'Z') fc += 32;
|
||||
if (qc >= 'A' && qc <= 'Z') qc += 32;
|
||||
|
||||
if (fc == qc) {
|
||||
score += 10;
|
||||
consecutive++;
|
||||
if (consecutive > 1) score += 5;
|
||||
query_idx++;
|
||||
} else {
|
||||
consecutive = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (query_idx < str_len(query)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (str_starts_with(filename, query)) {
|
||||
score += 20;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
static void index_walk_directory(const char *path, int depth) {
|
||||
if (depth > 16 || g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_starts_with(path, "/proc") ||
|
||||
str_starts_with(path, "/sys") ||
|
||||
str_starts_with(path, "/dev")) {
|
||||
return;
|
||||
}
|
||||
|
||||
vfs_dirent_t *entries = (vfs_dirent_t *)kmalloc(sizeof(vfs_dirent_t) * 1024);
|
||||
if (!entries) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = vfs_list_directory(path, entries, 1024);
|
||||
|
||||
if (count <= 0 || count > 1024) {
|
||||
kfree(entries);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
|
||||
break;
|
||||
}
|
||||
|
||||
vfs_dirent_t *entry = &entries[i];
|
||||
if (!entry) continue;
|
||||
|
||||
|
||||
if (!entry->name || entry->name[0] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (str_cmp(entry->name, ".color") == 0 ||
|
||||
str_cmp(entry->name, ".origin") == 0 ||
|
||||
str_cmp(entry->name, ".") == 0 ||
|
||||
str_cmp(entry->name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char full_path[FILE_INDEX_MAX_PATH];
|
||||
int path_len = 0;
|
||||
|
||||
|
||||
for (int j = 0; path[j] && path_len < FILE_INDEX_MAX_PATH - 1; j++) {
|
||||
full_path[path_len++] = path[j];
|
||||
}
|
||||
|
||||
|
||||
if (path_len > 0 && full_path[path_len - 1] != '/' && path_len < FILE_INDEX_MAX_PATH - 1) {
|
||||
full_path[path_len++] = '/';
|
||||
}
|
||||
|
||||
|
||||
for (int j = 0; entry->name[j] && path_len < FILE_INDEX_MAX_PATH - 1; j++) {
|
||||
full_path[path_len++] = entry->name[j];
|
||||
}
|
||||
full_path[path_len] = 0;
|
||||
|
||||
|
||||
if (path_len >= FILE_INDEX_MAX_PATH - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
file_index_entry_t *idx_entry = &g_file_index.entries[g_file_index.count];
|
||||
str_copy(idx_entry->path, full_path);
|
||||
idx_entry->size = entry->size;
|
||||
idx_entry->mod_time_low = entry->write_date;
|
||||
idx_entry->mod_time_high = entry->write_time;
|
||||
idx_entry->is_directory = entry->is_directory;
|
||||
|
||||
g_file_index.count++;
|
||||
|
||||
|
||||
if (entry->is_directory && !str_starts_with(full_path, "/proc") &&
|
||||
!str_starts_with(full_path, "/sys") && !str_starts_with(full_path, "/dev")) {
|
||||
index_walk_directory(full_path, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
kfree(entries);
|
||||
}
|
||||
|
||||
|
||||
void file_index_init(void) {
|
||||
g_file_index.count = 0;
|
||||
g_file_index.capacity = FILE_INDEX_MAX_ENTRIES;
|
||||
g_index_valid = false;
|
||||
}
|
||||
|
||||
|
||||
bool file_index_build(void) {
|
||||
|
||||
|
||||
|
||||
|
||||
g_file_index.count = 0;
|
||||
|
||||
|
||||
const char *safe_paths[] = {"/root", "/bin", "/Library", "/docs", NULL};
|
||||
|
||||
|
||||
for (int p = 0; safe_paths[p] != NULL && g_file_index.count < FILE_INDEX_MAX_ENTRIES; p++) {
|
||||
index_walk_directory(safe_paths[p], 0);
|
||||
}
|
||||
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
g_index_valid = true;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
|
||||
|
||||
file_index_save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool file_index_load(void) {
|
||||
vfs_file_t *file = vfs_open(FILE_INDEX_CACHE_PATH, "r");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint32_t version = 0;
|
||||
if (vfs_read(file, &version, sizeof(version)) != sizeof(version)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (version != FILE_INDEX_VERSION) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int count = 0;
|
||||
if (vfs_read(file, &count, sizeof(count)) != sizeof(count)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (count < 0 || count > FILE_INDEX_MAX_ENTRIES) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
file_index_entry_t temp_entries[FILE_INDEX_MAX_ENTRIES];
|
||||
for (int i = 0; i < count; i++) {
|
||||
file_index_entry_t *entry = &temp_entries[i];
|
||||
|
||||
int bytes_read = vfs_read(file, entry->path, FILE_INDEX_MAX_PATH);
|
||||
if (bytes_read != FILE_INDEX_MAX_PATH) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_read(file, &entry->size, sizeof(entry->size)) != sizeof(entry->size)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_read(file, &entry->mod_time_low, sizeof(entry->mod_time_low)) != sizeof(entry->mod_time_low)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_read(file, &entry->mod_time_high, sizeof(entry->mod_time_high)) != sizeof(entry->mod_time_high)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_read(file, &entry->is_directory, sizeof(entry->is_directory)) != sizeof(entry->is_directory)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_close(file);
|
||||
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
g_file_index.count = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
g_file_index.entries[i] = temp_entries[i];
|
||||
g_file_index.count++;
|
||||
}
|
||||
g_index_valid = true;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool file_index_save(void) {
|
||||
if (!vfs_mkdir("/Library")) {
|
||||
}
|
||||
if (!vfs_mkdir("/Library/Index")) {
|
||||
}
|
||||
|
||||
vfs_file_t *file = vfs_open(FILE_INDEX_CACHE_PATH, "w");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
int count = g_file_index.count;
|
||||
file_index_entry_t entries[FILE_INDEX_MAX_ENTRIES];
|
||||
for (int i = 0; i < count; i++) {
|
||||
entries[i] = g_file_index.entries[i];
|
||||
}
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
|
||||
|
||||
uint32_t version = FILE_INDEX_VERSION;
|
||||
if (vfs_write(file, &version, sizeof(version)) != sizeof(version)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_write(file, &count, sizeof(count)) != sizeof(count)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
file_index_entry_t *entry = &entries[i];
|
||||
|
||||
if (vfs_write(file, entry->path, FILE_INDEX_MAX_PATH) != FILE_INDEX_MAX_PATH) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_write(file, &entry->size, sizeof(entry->size)) != sizeof(entry->size)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_write(file, &entry->mod_time_low, sizeof(entry->mod_time_low)) != sizeof(entry->mod_time_low)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_write(file, &entry->mod_time_high, sizeof(entry->mod_time_high)) != sizeof(entry->mod_time_high)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vfs_write(file, &entry->is_directory, sizeof(entry->is_directory)) != sizeof(entry->is_directory)) {
|
||||
vfs_close(file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_close(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
int file_index_find_fuzzy(const char *query, file_index_result_t *results, int max_results) {
|
||||
if (!query || !results || max_results <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
|
||||
int result_count = 0;
|
||||
|
||||
for (int i = 0; i < g_file_index.count && result_count < max_results; i++) {
|
||||
if (i < 0 || i >= FILE_INDEX_MAX_ENTRIES) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char *path = g_file_index.entries[i].path;
|
||||
if (!path || path[0] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *filename = path;
|
||||
|
||||
for (int j = 0; path[j]; j++) {
|
||||
if (path[j] == '/') {
|
||||
filename = &path[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!filename || filename[0] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int score = fuzzy_match_score(query, filename);
|
||||
|
||||
if (score > 0) {
|
||||
results[result_count].entry = g_file_index.entries[i];
|
||||
results[result_count].match_score = score;
|
||||
result_count++;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
|
||||
for (int i = 0; i < result_count; i++) {
|
||||
for (int j = i + 1; j < result_count; j++) {
|
||||
if (results[j].match_score > results[i].match_score) {
|
||||
file_index_result_t tmp = results[i];
|
||||
results[i] = results[j];
|
||||
results[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result_count;
|
||||
}
|
||||
|
||||
bool file_index_add_entry(const char *path, uint32_t size, uint32_t mod_time_low,
|
||||
uint32_t mod_time_high, bool is_dir) {
|
||||
if (!path || g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
|
||||
for (int i = 0; i < g_file_index.count; i++) {
|
||||
if (str_cmp(g_file_index.entries[i].path, path) == 0) {
|
||||
g_file_index.entries[i].size = size;
|
||||
g_file_index.entries[i].mod_time_low = mod_time_low;
|
||||
g_file_index.entries[i].mod_time_high = mod_time_high;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
file_index_entry_t *entry = &g_file_index.entries[g_file_index.count];
|
||||
str_copy(entry->path, path);
|
||||
entry->size = size;
|
||||
entry->mod_time_low = mod_time_low;
|
||||
entry->mod_time_high = mod_time_high;
|
||||
entry->is_directory = is_dir;
|
||||
|
||||
g_file_index.count++;
|
||||
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool file_index_remove_entry(const char *path) {
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
|
||||
for (int i = 0; i < g_file_index.count; i++) {
|
||||
if (str_cmp(g_file_index.entries[i].path, path) == 0) {
|
||||
for (int j = i; j < g_file_index.count - 1; j++) {
|
||||
g_file_index.entries[j] = g_file_index.entries[j + 1];
|
||||
}
|
||||
g_file_index.count--;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
int file_index_get_entry_count(void) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
int count = g_file_index.count;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return count;
|
||||
}
|
||||
|
||||
void file_index_clear(void) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
g_file_index.count = 0;
|
||||
g_index_valid = false;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
}
|
||||
|
||||
void file_index_invalidate_cache(void) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
g_index_valid = false;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
}
|
||||
|
||||
bool file_index_is_valid(void) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
|
||||
bool valid = g_index_valid;
|
||||
spinlock_release_irqrestore(&g_index_lock, flags);
|
||||
return valid;
|
||||
}
|
||||
50
src/sys/file_index.h
Normal file
50
src/sys/file_index.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 FILE_INDEX_H
|
||||
#define FILE_INDEX_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define FILE_INDEX_MAX_ENTRIES 50000
|
||||
#define FILE_INDEX_MAX_PATH 1024
|
||||
#define FILE_INDEX_CACHE_PATH "/Library/Index/file_index.dat"
|
||||
#define FILE_INDEX_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
char path[FILE_INDEX_MAX_PATH];
|
||||
uint32_t size;
|
||||
uint32_t mod_time_low;
|
||||
uint32_t mod_time_high;
|
||||
bool is_directory;
|
||||
} file_index_entry_t;
|
||||
|
||||
typedef struct {
|
||||
file_index_entry_t entry;
|
||||
int match_score;
|
||||
} file_index_result_t;
|
||||
|
||||
typedef struct {
|
||||
file_index_entry_t entries[FILE_INDEX_MAX_ENTRIES];
|
||||
int count;
|
||||
int capacity;
|
||||
} file_index_t;
|
||||
|
||||
void file_index_init(void);
|
||||
bool file_index_build(void);
|
||||
bool file_index_load(void);
|
||||
bool file_index_save(void);
|
||||
|
||||
int file_index_find_fuzzy(const char *query, file_index_result_t *results, int max_results);
|
||||
|
||||
bool file_index_add_entry(const char *path, uint32_t size, uint32_t mod_time_low, uint32_t mod_time_high, bool is_dir);
|
||||
bool file_index_remove_entry(const char *path);
|
||||
|
||||
int file_index_get_entry_count(void);
|
||||
void file_index_clear(void);
|
||||
void file_index_invalidate_cache(void);
|
||||
bool file_index_is_valid(void);
|
||||
|
||||
#endif
|
||||
@@ -140,12 +140,12 @@ static void pic_remap(void) {
|
||||
static void pit_setup(void) {
|
||||
uint16_t divisor = 1193182 / 60; // ~60Hz
|
||||
|
||||
// Send command byte
|
||||
outb(0x43, 0x36); // Channel 0, lobyte/hibyte, mode 3 (square wave), binary
|
||||
// Mode 2: Rate Generator (more appropriate for periodic interrupts)
|
||||
outb(0x43, 0x34); io_wait(); // Channel 0, lobyte/hibyte, mode 2, binary
|
||||
|
||||
// Send divisor
|
||||
outb(0x40, divisor & 0xFF);
|
||||
outb(0x40, (divisor >> 8) & 0xFF);
|
||||
outb(0x40, divisor & 0xFF); io_wait();
|
||||
outb(0x40, (divisor >> 8) & 0xFF); io_wait();
|
||||
}
|
||||
|
||||
void idt_init(void) {
|
||||
|
||||
73
src/sys/kernel_subsystem.c
Normal file
73
src/sys/kernel_subsystem.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "kernel_subsystem.h"
|
||||
#include "memory_manager.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
static kernel_subsystem_t subsystems[MAX_SUBSYSTEMS];
|
||||
static int subsystem_count = 0;
|
||||
static spinlock_t sub_lock = SPINLOCK_INIT;
|
||||
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
|
||||
static void sub_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static int sub_strcmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
void subsystem_register(const char *name, kernel_subsystem_t **out_sub) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&sub_lock);
|
||||
|
||||
if (subsystem_count >= MAX_SUBSYSTEMS) {
|
||||
spinlock_release_irqrestore(&sub_lock, flags);
|
||||
if (out_sub) *out_sub = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already exists
|
||||
for (int i = 0; i < subsystem_count; i++) {
|
||||
if (sub_strcmp(subsystems[i].name, name) == 0) {
|
||||
spinlock_release_irqrestore(&sub_lock, flags);
|
||||
if (out_sub) *out_sub = &subsystems[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
kernel_subsystem_t *s = &subsystems[subsystem_count++];
|
||||
mem_memset(s, 0, sizeof(kernel_subsystem_t));
|
||||
sub_strcpy(s->name, name);
|
||||
|
||||
spinlock_release_irqrestore(&sub_lock, flags);
|
||||
if (out_sub) *out_sub = s;
|
||||
}
|
||||
|
||||
void subsystem_add_file(kernel_subsystem_t *sub, const char *name,
|
||||
int (*read)(char*, int, int),
|
||||
int (*write)(const char*, int, int)) {
|
||||
if (!sub || sub->file_count >= MAX_SUBSYSTEM_FILES) return;
|
||||
|
||||
subsystem_file_t *f = &sub->files[sub->file_count++];
|
||||
sub_strcpy(f->name, name);
|
||||
f->read = read;
|
||||
f->write = write;
|
||||
}
|
||||
|
||||
kernel_subsystem_t* subsystem_get_by_name(const char *name) {
|
||||
for (int i = 0; i < subsystem_count; i++) {
|
||||
if (sub_strcmp(subsystems[i].name, name) == 0) return &subsystems[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int subsystem_get_count(void) {
|
||||
return subsystem_count;
|
||||
}
|
||||
|
||||
kernel_subsystem_t* subsystem_get_by_index(int index) {
|
||||
if (index < 0 || index >= subsystem_count) return NULL;
|
||||
return &subsystems[index];
|
||||
}
|
||||
32
src/sys/kernel_subsystem.h
Normal file
32
src/sys/kernel_subsystem.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef KERNEL_SUBSYSTEM_H
|
||||
#define KERNEL_SUBSYSTEM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_SUBSYSTEMS 16
|
||||
#define MAX_SUBSYSTEM_FILES 32
|
||||
|
||||
typedef struct {
|
||||
char name[64];
|
||||
int (*read)(char *buffer, int size, int offset);
|
||||
int (*write)(const char *buffer, int size, int offset);
|
||||
} subsystem_file_t;
|
||||
|
||||
typedef struct {
|
||||
char name[64];
|
||||
subsystem_file_t files[MAX_SUBSYSTEM_FILES];
|
||||
int file_count;
|
||||
} kernel_subsystem_t;
|
||||
|
||||
void subsystem_register(const char *name, kernel_subsystem_t **out_sub);
|
||||
void subsystem_add_file(kernel_subsystem_t *sub, const char *name,
|
||||
int (*read)(char*, int, int),
|
||||
int (*write)(const char*, int, int));
|
||||
|
||||
kernel_subsystem_t* subsystem_get_by_name(const char *name);
|
||||
int subsystem_get_count(void);
|
||||
kernel_subsystem_t* subsystem_get_by_index(int index);
|
||||
|
||||
#endif
|
||||
29
src/sys/module_manager.c
Normal file
29
src/sys/module_manager.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "module_manager.h"
|
||||
#include "memory_manager.h"
|
||||
|
||||
#define MAX_MODULES 32
|
||||
static kernel_module_t modules[MAX_MODULES];
|
||||
static int module_count = 0;
|
||||
|
||||
static void mod_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
void module_manager_register(const char *name, uint64_t addr, uint64_t size) {
|
||||
if (module_count >= MAX_MODULES) return;
|
||||
|
||||
kernel_module_t *m = &modules[module_count++];
|
||||
mod_strcpy(m->name, name);
|
||||
m->address = addr;
|
||||
m->size = size;
|
||||
}
|
||||
|
||||
int module_manager_get_count(void) {
|
||||
return module_count;
|
||||
}
|
||||
|
||||
kernel_module_t* module_manager_get_index(int index) {
|
||||
if (index < 0 || index >= module_count) return NULL;
|
||||
return &modules[index];
|
||||
}
|
||||
17
src/sys/module_manager.h
Normal file
17
src/sys/module_manager.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef MODULE_MANAGER_H
|
||||
#define MODULE_MANAGER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
char name[64];
|
||||
uint64_t address;
|
||||
uint64_t size;
|
||||
} kernel_module_t;
|
||||
|
||||
void module_manager_register(const char *name, uint64_t addr, uint64_t size);
|
||||
int module_manager_get_count(void);
|
||||
kernel_module_t* module_manager_get_index(int index);
|
||||
|
||||
#endif
|
||||
@@ -20,30 +20,31 @@ extern void serial_write(const char *str);
|
||||
#define MAX_PROCESSES 16
|
||||
#define MAX_CPUS_SCHED 32
|
||||
process_t processes[MAX_PROCESSES] __attribute__((aligned(16)));
|
||||
int process_count = 0;
|
||||
static process_t* current_process[MAX_CPUS_SCHED] = {0}; // Per-CPU
|
||||
static uint32_t next_pid = 0;
|
||||
static void *free_kernel_stack_later = NULL;
|
||||
static uint64_t free_pml4_later = 0;
|
||||
static void *free_kernel_stack_later[MAX_CPUS_SCHED] = {0};
|
||||
static uint64_t free_pml4_later[MAX_CPUS_SCHED] = {0};
|
||||
static spinlock_t runqueue_lock = SPINLOCK_INIT;
|
||||
static uint32_t next_cpu_assign = 1; // Round-robin CPU assignment (start from CPU 1)
|
||||
static uint32_t next_cpu_assign = 1;
|
||||
|
||||
static void process_cleanup_inner(process_t *proc);
|
||||
|
||||
void process_init(void) {
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
processes[i].pid = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Current kernel execution is PID 0
|
||||
process_t *kernel_proc = &processes[process_count++];
|
||||
process_t *kernel_proc = &processes[0];
|
||||
kernel_proc->pid = next_pid++;
|
||||
kernel_proc->is_user = false;
|
||||
kernel_proc->is_idle = true;
|
||||
kernel_proc->tty_id = -1;
|
||||
kernel_proc->kill_pending = false;
|
||||
|
||||
|
||||
// We don't have its RSP or PML4 yet, but it's already running.
|
||||
// The timer interrupt will naturally capture its context on the first tick!
|
||||
kernel_proc->pml4_phys = paging_get_pml4_phys();
|
||||
kernel_proc->kernel_stack = 0;
|
||||
|
||||
// Initialize FPU/SSE state for kernel (first interrupt will capture it on stack)
|
||||
kernel_proc->fpu_initialized = true;
|
||||
|
||||
for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL;
|
||||
@@ -55,20 +56,41 @@ void process_init(void) {
|
||||
|
||||
kernel_proc->next = kernel_proc; // Circular linked list
|
||||
kernel_proc->cpu_affinity = 0; // Kernel always on BSP
|
||||
mem_memset(kernel_proc->cwd, 0, 1024);
|
||||
kernel_proc->cwd[0] = '/';
|
||||
current_process[0] = kernel_proc;
|
||||
}
|
||||
|
||||
process_t* process_create(void (*entry_point)(void), bool is_user) {
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
|
||||
if (process_count >= MAX_PROCESSES) {
|
||||
process_t *new_proc = NULL;
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid == 0xFFFFFFFF) {
|
||||
new_proc = &processes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!new_proc) {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
process_t *new_proc = &processes[process_count++];
|
||||
new_proc->pid = next_pid++;
|
||||
new_proc->is_user = is_user;
|
||||
new_proc->tty_id = -1;
|
||||
new_proc->kill_pending = false;
|
||||
|
||||
process_t *parent = process_get_current();
|
||||
if (parent) {
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy(new_proc->cwd, parent->cwd, 1024);
|
||||
new_proc->tty_id = parent->tty_id;
|
||||
} else {
|
||||
mem_memset(new_proc->cwd, 0, 1024);
|
||||
new_proc->cwd[0] = '/';
|
||||
}
|
||||
|
||||
// 1. Setup Page Table
|
||||
if (is_user) {
|
||||
@@ -157,15 +179,14 @@ process_t* process_create(void (*entry_point)(void), bool is_user) {
|
||||
return new_proc;
|
||||
}
|
||||
|
||||
process_t* process_create_elf(const char* filepath, const char* args_str) {
|
||||
process_t* process_create_elf(const char* filepath, const char* args_str, bool terminal_proc, int tty_id) {
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
process_t *new_proc = NULL;
|
||||
|
||||
// Find an available slot
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid == 0xFFFFFFFF || i >= process_count) {
|
||||
if (processes[i].pid == 0xFFFFFFFF) {
|
||||
new_proc = &processes[i];
|
||||
if (i >= process_count) process_count = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -189,16 +210,27 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
||||
new_proc->ui_window = NULL;
|
||||
new_proc->heap_start = 0x20000000; // 512MB mark
|
||||
new_proc->heap_end = 0x20000000;
|
||||
new_proc->is_terminal_proc = false;
|
||||
new_proc->is_terminal_proc = terminal_proc;
|
||||
new_proc->tty_id = tty_id;
|
||||
new_proc->kill_pending = false;
|
||||
|
||||
process_t *parent = process_get_current();
|
||||
if (parent) {
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy(new_proc->cwd, parent->cwd, 1024);
|
||||
} else {
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
mem_memset(new_proc->cwd, 0, 1024);
|
||||
new_proc->cwd[0] = '/';
|
||||
}
|
||||
|
||||
// 2. Load ELF executable
|
||||
size_t elf_load_size = 0;
|
||||
uint64_t entry_point = elf_load(filepath, new_proc->pml4_phys, &elf_load_size);
|
||||
if (entry_point == 0) {
|
||||
serial_write("[PROCESS] Failed to load ELF: ");
|
||||
serial_write("[PROC] Failed to load ELF: ");
|
||||
serial_write(filepath);
|
||||
serial_write("\n");
|
||||
// We technically leak the page table here, but let's ignore cleanup for now
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -354,7 +386,7 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
||||
current_process[0]->next = new_proc;
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
serial_write("[PROCESS] Spawned ELF Executable: ");
|
||||
serial_write("[PROC] Exec: ");
|
||||
serial_write(filepath);
|
||||
serial_write("\n");
|
||||
return new_proc;
|
||||
@@ -376,25 +408,102 @@ process_t* process_get_current(void) {
|
||||
}
|
||||
|
||||
uint64_t process_schedule(uint64_t current_rsp) {
|
||||
if (free_kernel_stack_later) {
|
||||
kfree(free_kernel_stack_later);
|
||||
free_kernel_stack_later = NULL;
|
||||
uint32_t my_cpu = smp_this_cpu_id();
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
|
||||
void *cleanup_stack = NULL;
|
||||
uint64_t cleanup_pml4 = 0;
|
||||
|
||||
if (free_kernel_stack_later[my_cpu]) {
|
||||
cleanup_stack = free_kernel_stack_later[my_cpu];
|
||||
free_kernel_stack_later[my_cpu] = NULL;
|
||||
}
|
||||
if (free_pml4_later) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(free_pml4_later);
|
||||
free_pml4_later = 0;
|
||||
if (free_pml4_later[my_cpu]) {
|
||||
cleanup_pml4 = free_pml4_later[my_cpu];
|
||||
free_pml4_later[my_cpu] = 0;
|
||||
}
|
||||
|
||||
uint32_t my_cpu = smp_this_cpu_id();
|
||||
process_t *cur = current_process[my_cpu];
|
||||
|
||||
if (!cur || !cur->next || cur == cur->next)
|
||||
if (!cur || !cur->next || cur == cur->next) {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
// Perform cleanup outside the lock
|
||||
if (cleanup_stack) kfree(cleanup_stack);
|
||||
if (cleanup_pml4) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(cleanup_pml4);
|
||||
}
|
||||
|
||||
return current_rsp;
|
||||
}
|
||||
|
||||
// Save context
|
||||
cur->rsp = current_rsp;
|
||||
|
||||
if (cur->kill_pending && cur->pid != 0xFFFFFFFF && cur->pid != 0) {
|
||||
process_cleanup_inner(cur);
|
||||
|
||||
process_t *prev = cur;
|
||||
while (prev->next != cur) {
|
||||
prev = prev->next;
|
||||
}
|
||||
|
||||
if (prev != cur) {
|
||||
prev->next = cur->next;
|
||||
|
||||
process_t *next_proc = cur->next;
|
||||
while (next_proc != cur) {
|
||||
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) break;
|
||||
next_proc = next_proc->next;
|
||||
}
|
||||
|
||||
if (next_proc == cur || next_proc->cpu_affinity != my_cpu) {
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) {
|
||||
next_proc = &processes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current_process[my_cpu] = next_proc;
|
||||
|
||||
cur->pid = 0xFFFFFFFF;
|
||||
cur->cpu_affinity = 0xFFFFFFFF;
|
||||
cur->ui_window = NULL;
|
||||
cur->is_terminal_proc = false;
|
||||
cur->kill_pending = false;
|
||||
|
||||
free_kernel_stack_later[my_cpu] = cur->kernel_stack_alloc;
|
||||
cur->kernel_stack_alloc = NULL;
|
||||
free_pml4_later[my_cpu] = cur->pml4_phys;
|
||||
cur->pml4_phys = 0;
|
||||
|
||||
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
|
||||
cpu_state_t *cpu_state = smp_get_cpu(my_cpu);
|
||||
if (cpu_state) {
|
||||
cpu_state->kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||
}
|
||||
}
|
||||
|
||||
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||
|
||||
current_process[my_cpu]->ticks++;
|
||||
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
if (cleanup_stack) kfree(cleanup_stack);
|
||||
if (cleanup_pml4) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(cleanup_pml4);
|
||||
}
|
||||
|
||||
return next_rsp;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to next ready process assigned to this CPU
|
||||
extern uint32_t wm_get_ticks(void);
|
||||
uint32_t now = wm_get_ticks();
|
||||
@@ -404,7 +513,7 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
||||
|
||||
while (next_proc != start) {
|
||||
// Only consider processes assigned to our CPU and not terminated
|
||||
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) {
|
||||
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) {
|
||||
if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) {
|
||||
break;
|
||||
}
|
||||
@@ -412,11 +521,8 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
||||
next_proc = next_proc->next;
|
||||
}
|
||||
|
||||
// If we didn't find a ready process for our CPU, stay on current (unless we are terminated)
|
||||
if (next_proc->cpu_affinity != my_cpu || next_proc->pid == 0xFFFFFFFF) {
|
||||
// Fallback to idle if current is terminated
|
||||
if (cur && cur->pid == 0xFFFFFFFF) {
|
||||
// Find the idle process for this CPU
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) {
|
||||
next_proc = &processes[i];
|
||||
@@ -424,18 +530,25 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
if (cleanup_stack) kfree(cleanup_stack);
|
||||
if (cleanup_pml4) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(cleanup_pml4);
|
||||
}
|
||||
|
||||
return current_rsp;
|
||||
}
|
||||
}
|
||||
|
||||
current_process[my_cpu] = next_proc;
|
||||
|
||||
// Update Kernel Stack for User Mode interrupts and System Calls
|
||||
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
|
||||
if (my_cpu == 0) {
|
||||
extern uint64_t kernel_syscall_stack;
|
||||
kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||
cpu_state_t *cpu_state = smp_get_cpu(my_cpu);
|
||||
if (cpu_state) {
|
||||
cpu_state->kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,8 +556,16 @@ uint64_t process_schedule(uint64_t current_rsp) {
|
||||
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||
|
||||
current_process[my_cpu]->ticks++;
|
||||
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||
|
||||
return current_process[my_cpu]->rsp;
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
if (cleanup_stack) kfree(cleanup_stack);
|
||||
if (cleanup_pml4) {
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
paging_destroy_user_pml4_phys(cleanup_pml4);
|
||||
}
|
||||
|
||||
return next_rsp;
|
||||
}
|
||||
|
||||
process_t* process_get_by_pid(uint32_t pid) {
|
||||
@@ -454,12 +575,20 @@ process_t* process_get_by_pid(uint32_t pid) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void process_kill_by_tty(int tty_id) {
|
||||
if (tty_id < 0) return;
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].tty_id == tty_id) {
|
||||
process_terminate(&processes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_cleanup_inner(process_t *proc) {
|
||||
if (!proc || proc->pid == 0xFFFFFFFF) return;
|
||||
|
||||
// 1. Cleanup side effects
|
||||
extern Window win_cmd;
|
||||
if (proc->ui_window && (proc->ui_window != &win_cmd)) {
|
||||
if (proc->ui_window) {
|
||||
wm_remove_window((Window *)proc->ui_window);
|
||||
proc->ui_window = NULL;
|
||||
}
|
||||
@@ -486,6 +615,14 @@ static void process_cleanup_inner(process_t *proc) {
|
||||
void process_terminate(process_t *to_delete) {
|
||||
if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return;
|
||||
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) {
|
||||
if (current_process[c] == to_delete) {
|
||||
to_delete->kill_pending = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
|
||||
process_cleanup_inner(to_delete);
|
||||
@@ -506,7 +643,6 @@ void process_terminate(process_t *to_delete) {
|
||||
prev->next = to_delete->next;
|
||||
|
||||
// Update per-CPU current_process if this was the current on any CPU
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) {
|
||||
if (current_process[c] == to_delete) {
|
||||
process_t *np = to_delete->next;
|
||||
@@ -528,6 +664,7 @@ void process_terminate(process_t *to_delete) {
|
||||
// Mark slot as free
|
||||
to_delete->pid = 0xFFFFFFFF;
|
||||
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||
to_delete->kill_pending = false;
|
||||
|
||||
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
||||
// Defer kernel stack until we switch away from it
|
||||
@@ -596,29 +733,22 @@ uint64_t process_terminate_current(void) {
|
||||
to_delete->cpu_affinity = 0xFFFFFFFF;
|
||||
to_delete->ui_window = NULL;
|
||||
to_delete->is_terminal_proc = false;
|
||||
to_delete->kill_pending = false;
|
||||
|
||||
// 4. Load context for the NEXT process
|
||||
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
|
||||
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
|
||||
if (my_cpu == 0) {
|
||||
extern uint64_t kernel_syscall_stack;
|
||||
kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||
cpu_state_t *cpu_state = smp_get_cpu(my_cpu);
|
||||
if (cpu_state) {
|
||||
cpu_state->kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
|
||||
}
|
||||
}
|
||||
|
||||
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||
|
||||
// 5. Free memory
|
||||
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
|
||||
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
if (to_delete->pml4_phys && to_delete->is_user) {
|
||||
paging_destroy_user_pml4_phys(to_delete->pml4_phys);
|
||||
}
|
||||
|
||||
to_delete->user_stack_alloc = NULL;
|
||||
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
||||
free_kernel_stack_later[my_cpu] = to_delete->kernel_stack_alloc;
|
||||
to_delete->kernel_stack_alloc = NULL;
|
||||
free_pml4_later[my_cpu] = to_delete->pml4_phys;
|
||||
to_delete->pml4_phys = 0;
|
||||
|
||||
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||
@@ -666,4 +796,3 @@ process_t* process_get_by_ui_window(void *win) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ typedef struct process {
|
||||
void *user_stack_alloc;
|
||||
|
||||
bool is_terminal_proc;
|
||||
int tty_id;
|
||||
bool kill_pending;
|
||||
|
||||
struct process *next;
|
||||
|
||||
@@ -52,7 +54,9 @@ typedef struct process {
|
||||
uint64_t ticks;
|
||||
uint64_t sleep_until;
|
||||
size_t used_memory;
|
||||
uint32_t cpu_affinity; // Which CPU this process runs on (0 = BSP)
|
||||
uint32_t cpu_affinity;
|
||||
bool is_idle;
|
||||
char cwd[1024];
|
||||
} __attribute__((aligned(16))) process_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -60,11 +64,12 @@ typedef struct {
|
||||
char name[64];
|
||||
uint64_t ticks;
|
||||
size_t used_memory;
|
||||
bool is_idle;
|
||||
} ProcessInfo;
|
||||
|
||||
void process_init(void);
|
||||
process_t* process_create(void (*entry_point)(void), bool is_user);
|
||||
process_t* process_create_elf(const char* filepath, const char* args_str);
|
||||
process_t* process_create_elf(const char* filepath, const char* args_str, bool terminal_proc, int tty_id);
|
||||
process_t* process_get_current(void);
|
||||
void process_set_current_for_cpu(uint32_t cpu_id, process_t* p);
|
||||
process_t* process_get_current_for_cpu(uint32_t cpu_id);
|
||||
@@ -72,8 +77,9 @@ uint64_t process_schedule(uint64_t current_rsp);
|
||||
uint64_t process_terminate_current(void);
|
||||
void process_terminate(process_t *proc);
|
||||
process_t* process_get_by_pid(uint32_t pid);
|
||||
void process_kill_by_tty(int tty_id);
|
||||
|
||||
// SMP: IPI handler for AP scheduling (called from ISR)
|
||||
// SMP: IPI handler for AP scheduling
|
||||
uint64_t sched_ipi_handler(registers_t *regs);
|
||||
|
||||
void process_push_gui_event(process_t *proc, gui_event_t *ev);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "paging.h"
|
||||
#include "process.h"
|
||||
#include "work_queue.h"
|
||||
#include "core/kutils.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint32_t n);
|
||||
@@ -18,20 +19,36 @@ extern void serial_write_hex(uint64_t n);
|
||||
static cpu_state_t *cpu_states = NULL;
|
||||
static uint32_t total_cpus = 0;
|
||||
static uint32_t bsp_lapic_id = 0;
|
||||
static cpu_state_t *bsp_cpu_state = NULL;
|
||||
|
||||
#define MSR_GS_BASE 0xC0000101
|
||||
#define MSR_KERNEL_GS_BASE 0xC0000102
|
||||
|
||||
static inline void wrmsr(uint32_t msr, uint64_t value) {
|
||||
uint32_t low = (uint32_t)value;
|
||||
uint32_t high = (uint32_t)(value >> 32);
|
||||
asm volatile("wrmsr" : : "c"(msr), "a"(low), "d"(high));
|
||||
}
|
||||
|
||||
static uint32_t read_lapic_id(void) {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||
return (ebx >> 24) & 0xFF;
|
||||
extern uint64_t hhdm_offset;
|
||||
volatile uint32_t *lapic = (volatile uint32_t *)(hhdm_offset + 0xFEE00000ULL);
|
||||
return (lapic[0x020 / 4] >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
uint32_t smp_this_cpu_id(void) {
|
||||
if (total_cpus <= 1) return 0;
|
||||
if (!cpu_states || total_cpus == 0) return 0;
|
||||
|
||||
uint32_t lapic = read_lapic_id();
|
||||
if (lapic == bsp_lapic_id) return 0;
|
||||
cpu_state_t *state = NULL;
|
||||
asm volatile("movq %%gs:0, %0" : "=r"(state) : : "memory");
|
||||
if (state && state->lapic_id == lapic) return state->cpu_id;
|
||||
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||
if (cpu_states[i].lapic_id == lapic) return i;
|
||||
if (cpu_states[i].online && cpu_states[i].lapic_id == lapic) return i;
|
||||
}
|
||||
return 0; // Fallback to BSP
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smp_cpu_count(void) {
|
||||
@@ -68,13 +85,21 @@ static void ap_entry(struct limine_smp_info *info) {
|
||||
extern void idt_load(void);
|
||||
idt_load();
|
||||
|
||||
extern void syscall_init(void);
|
||||
syscall_init();
|
||||
|
||||
uint64_t kernel_cr3 = paging_get_pml4_phys();
|
||||
asm volatile("mov %0, %%cr3" : : "r"(kernel_cr3));
|
||||
|
||||
extern void lapic_enable(void);
|
||||
lapic_enable();
|
||||
|
||||
cpu_states[my_id].self = &cpu_states[my_id];
|
||||
cpu_states[my_id].online = true;
|
||||
cpu_states[my_id].kernel_syscall_stack = cpu_states[my_id].kernel_stack;
|
||||
|
||||
wrmsr(MSR_GS_BASE, (uint64_t)&cpu_states[my_id]);
|
||||
wrmsr(MSR_KERNEL_GS_BASE, (uint64_t)&cpu_states[my_id]);
|
||||
|
||||
serial_write("[SMP] AP ");
|
||||
serial_write_num(my_id);
|
||||
@@ -84,12 +109,31 @@ static void ap_entry(struct limine_smp_info *info) {
|
||||
|
||||
process_t *ap_idle = process_create(NULL, false);
|
||||
ap_idle->cpu_affinity = my_id;
|
||||
ap_idle->is_idle = true;
|
||||
k_strcpy(ap_idle->name, "idle:");
|
||||
char id_s[8]; k_itoa(my_id, id_s);
|
||||
k_strcpy(ap_idle->name + 5, id_s);
|
||||
|
||||
process_set_current_for_cpu(my_id, ap_idle);
|
||||
asm volatile("sti");
|
||||
|
||||
work_queue_drain_loop();
|
||||
}
|
||||
|
||||
void smp_init_bsp(void) {
|
||||
static cpu_state_t bsp_state_static = {0};
|
||||
bsp_state_static.cpu_id = 0;
|
||||
bsp_lapic_id = read_lapic_id();
|
||||
bsp_state_static.lapic_id = bsp_lapic_id;
|
||||
bsp_state_static.self = &bsp_state_static;
|
||||
bsp_state_static.online = true;
|
||||
|
||||
wrmsr(MSR_GS_BASE, (uint64_t)&bsp_state_static);
|
||||
wrmsr(MSR_KERNEL_GS_BASE, (uint64_t)&bsp_state_static);
|
||||
|
||||
bsp_cpu_state = &bsp_state_static;
|
||||
}
|
||||
|
||||
// --- SMP Initialization ---
|
||||
uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
if (!smp_resp || smp_resp->cpu_count <= 1) {
|
||||
@@ -132,8 +176,15 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) {
|
||||
cpu_states[i].lapic_id = cpu->lapic_id;
|
||||
|
||||
if (cpu->lapic_id == bsp_lapic_id) {
|
||||
cpu_states[i].online = true;
|
||||
cpu_states[i] = *bsp_cpu_state; // Copy early BSP state
|
||||
cpu_states[i].self = &cpu_states[i];
|
||||
|
||||
cpu_states[i].kernel_stack = 0; // Limine stack for now
|
||||
cpu_states[i].kernel_syscall_stack = 0;
|
||||
bsp_index = i;
|
||||
wrmsr(MSR_GS_BASE, (uint64_t)&cpu_states[i]);
|
||||
wrmsr(MSR_KERNEL_GS_BASE, (uint64_t)&cpu_states[i]);
|
||||
|
||||
serial_write("[SMP] BSP CPU ");
|
||||
serial_write_num(i);
|
||||
serial_write(" (LAPIC ");
|
||||
|
||||
@@ -8,29 +8,27 @@
|
||||
#include <stdbool.h>
|
||||
#include "spinlock.h"
|
||||
|
||||
// Per-CPU state. Dynamically allocated at boot based on actual CPU count.
|
||||
typedef struct cpu_state {
|
||||
uint32_t cpu_id; // Logical CPU index (0 = BSP)
|
||||
uint32_t lapic_id; // Local APIC ID from Limine
|
||||
uint64_t kernel_stack; // Top of kernel stack for this CPU
|
||||
void *kernel_stack_alloc; // Base allocation for kfree
|
||||
volatile bool online; // True once AP is fully initialized
|
||||
struct cpu_state *self;
|
||||
uint32_t cpu_id;
|
||||
uint32_t lapic_id;
|
||||
uint64_t kernel_stack;
|
||||
void *kernel_stack_alloc;
|
||||
volatile bool online;
|
||||
uint64_t user_rsp_scratch;
|
||||
uint64_t kernel_syscall_stack;
|
||||
} cpu_state_t;
|
||||
|
||||
// Initialize SMP — call after GDT/IDT/memory init but before wm_init.
|
||||
// Pass the Limine SMP response. APs will be started and will enter their
|
||||
// idle loops. Returns the number of CPUs brought online.
|
||||
void smp_init_bsp(void);
|
||||
|
||||
|
||||
struct limine_smp_response;
|
||||
uint32_t smp_init(struct limine_smp_response *smp_resp);
|
||||
|
||||
// Get the current CPU index (0 = BSP). Uses CPUID to read LAPIC ID,
|
||||
// then looks up in the cpu table.
|
||||
uint32_t smp_this_cpu_id(void);
|
||||
|
||||
// Total number of CPUs online.
|
||||
uint32_t smp_cpu_count(void);
|
||||
|
||||
// Get per-CPU state by index.
|
||||
cpu_state_t *smp_get_cpu(uint32_t cpu_id);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "process.h"
|
||||
#include "wm.h"
|
||||
#include "fat32.h"
|
||||
#include "vfs.h"
|
||||
#include "paging.h"
|
||||
#include "work_queue.h"
|
||||
#include "smp.h"
|
||||
@@ -18,11 +19,16 @@
|
||||
#include "network.h"
|
||||
#include "icmp.h"
|
||||
#include "cmd.h"
|
||||
#include "tty.h"
|
||||
#include "font_manager.h"
|
||||
#include "graphics.h"
|
||||
|
||||
extern bool ps2_ctrl_pressed;
|
||||
|
||||
#define SPAWN_FLAG_TERMINAL 0x1
|
||||
#define SPAWN_FLAG_INHERIT_TTY 0x2
|
||||
#define SPAWN_FLAG_TTY_ID 0x4
|
||||
|
||||
// Read MSR
|
||||
static inline uint64_t rdmsr(uint32_t msr) {
|
||||
uint32_t low, high;
|
||||
@@ -76,6 +82,14 @@ static void smp_user_wrapper(void *arg) {
|
||||
}
|
||||
|
||||
void syscall_init(void) {
|
||||
uint64_t efer = rdmsr(MSR_EFER);
|
||||
efer |= 1;
|
||||
wrmsr(MSR_EFER, efer);
|
||||
uint64_t star = ((uint64_t)0x001B << 48) | ((uint64_t)0x0008 << 32);
|
||||
wrmsr(MSR_STAR, star);
|
||||
extern void syscall_entry(void);
|
||||
wrmsr(MSR_LSTAR, (uint64_t)syscall_entry);
|
||||
wrmsr(MSR_FMASK, 0x200);
|
||||
}
|
||||
|
||||
static void user_window_close(Window *win) {
|
||||
@@ -171,6 +185,12 @@ static void user_window_resize(Window *win, int w, int h) {
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
mem_memset(win->pixels, 0, w * h * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
process_t *proc = process_get_by_ui_window(win);
|
||||
if (proc) {
|
||||
gui_event_t ev = { .type = GUI_EVENT_RESIZE, .arg1 = w, .arg2 = h };
|
||||
process_push_gui_event(proc, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,19 +207,36 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
|
||||
if (syscall_num == 1) { // SYS_WRITE
|
||||
extern void cmd_write_len(const char *str, size_t len);
|
||||
cmd_write_len((const char*)arg2, (size_t)arg3);
|
||||
process_t *proc = process_get_current();
|
||||
const char *buf = (const char*)arg2;
|
||||
size_t len = (size_t)arg3;
|
||||
if (!proc || !proc->is_user) {
|
||||
cmd_write_len(buf, len);
|
||||
return len;
|
||||
}
|
||||
if (proc->is_terminal_proc) {
|
||||
if (proc->tty_id >= 0) {
|
||||
tty_write_output(proc->tty_id, buf, len);
|
||||
return len;
|
||||
}
|
||||
cmd_write_len(buf, len);
|
||||
return len;
|
||||
}
|
||||
return len;
|
||||
} else if (syscall_num == 3) { // SYS_GUI
|
||||
int cmd = (int)arg1;
|
||||
process_t *proc = process_get_current();
|
||||
|
||||
if (cmd == GUI_CMD_WINDOW_CREATE) {
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("Kernel: GUI_CMD_WINDOW_CREATE\n");
|
||||
|
||||
const char *title = (const char *)arg2;
|
||||
|
||||
serial_write("[WM] CreateWindow: ");
|
||||
serial_write(title ? title : "Unknown");
|
||||
serial_write("\n");
|
||||
uint64_t *u_params = (uint64_t *)arg3;
|
||||
if (!u_params) {
|
||||
serial_write("Kernel: Error - params is NULL\n");
|
||||
serial_write("[WM] Error - params is NULL\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -207,14 +244,14 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
uint64_t params[4];
|
||||
for (int i = 0; i < 4; i++) params[i] = u_params[i];
|
||||
|
||||
serial_write("Kernel: Window params copied.\n");
|
||||
// params verified
|
||||
|
||||
Window *win = kmalloc(sizeof(Window));
|
||||
if (!win) {
|
||||
serial_write("Kernel: Error - kmalloc failed for Window\n");
|
||||
serial_write("[WM] Error - kmalloc failed for Window\n");
|
||||
return 0;
|
||||
}
|
||||
serial_write("Kernel: Window allocated.\n");
|
||||
// win allocated
|
||||
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
mem_memset(win, 0, sizeof(Window));
|
||||
@@ -231,11 +268,8 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
kernel_title[i] = title[i];
|
||||
}
|
||||
kernel_title[title_len] = '\0';
|
||||
serial_write("Kernel: Title copied: ");
|
||||
serial_write(kernel_title);
|
||||
serial_write("\n");
|
||||
} else {
|
||||
serial_write("Kernel: Warning - kernel_title kmalloc failed\n");
|
||||
serial_write("[WM] Warning: kernel_title kmalloc failed\n");
|
||||
}
|
||||
|
||||
// Basic initialization
|
||||
@@ -245,7 +279,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
win->w = (int)params[2];
|
||||
win->h = (int)params[3];
|
||||
|
||||
serial_write("Kernel: Init win dims.\n");
|
||||
// dims ready
|
||||
|
||||
// Sanity checks for dimensions
|
||||
if (win->w <= 0 || win->w > 4096) win->w = 400;
|
||||
@@ -259,7 +293,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
win->font = NULL;
|
||||
win->lock = SPINLOCK_INIT;
|
||||
|
||||
serial_write("Kernel: Dims initialized.\n");
|
||||
// ready
|
||||
|
||||
size_t pixel_size = 0;
|
||||
// Safe allocation
|
||||
@@ -274,7 +308,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
win->comp_pixels = kmalloc(pixel_size);
|
||||
}
|
||||
|
||||
serial_write("Kernel: Buffers allocated.\n");
|
||||
// buffs ok
|
||||
|
||||
if (win->pixels) {
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
@@ -285,7 +319,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
mem_memset(win->comp_pixels, 0, pixel_size);
|
||||
}
|
||||
|
||||
serial_write("Kernel: Buffers cleared.\n");
|
||||
serial_write("[WM] Buffers ready\n");
|
||||
|
||||
// Set callbacks
|
||||
win->paint = user_window_paint;
|
||||
@@ -298,6 +332,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
|
||||
proc->ui_window = win;
|
||||
wm_add_window(win);
|
||||
wm_mark_dirty(0, 0, get_screen_width(), 30);
|
||||
|
||||
return (uint64_t)win;
|
||||
} else if (cmd == GUI_CMD_DRAW_RECT) {
|
||||
@@ -406,10 +441,18 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = uy + font_manager_get_font_ascent_scaled(font, font->pixel_height) - 2;
|
||||
int cur_x = ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, font->pixel_height);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', font->pixel_height) * 4;
|
||||
} else {
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string(ux, uy, kernel_str, color);
|
||||
@@ -421,10 +464,18 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = win->y + uy + font_manager_get_font_ascent_scaled(font, font->pixel_height) - 2;
|
||||
int cur_x = win->x + ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, font->pixel_height);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', font->pixel_height) * 4;
|
||||
} else {
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, font->pixel_height, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, font->pixel_height);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string(win->x + ux, win->y + uy, kernel_str, color);
|
||||
@@ -510,10 +561,18 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||
int cur_x = ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, scale);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', scale) * 4;
|
||||
} else {
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string_scaled(ux, uy, kernel_str, color, scale);
|
||||
@@ -525,10 +584,18 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = win->y + uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||
int cur_x = win->x + ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, scale);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', scale) * 4;
|
||||
} else {
|
||||
font_manager_render_char_scaled(font, cur_x, baseline, codepoint, color, scale, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string_scaled(win->x + ux, win->y + uy, kernel_str, color, scale);
|
||||
@@ -586,11 +653,19 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||
int cur_x = ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t));
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, scale);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', scale) * 4;
|
||||
} else {
|
||||
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string_scaled_sloped(ux, uy, kernel_str, color, scale, slope);
|
||||
@@ -602,11 +677,19 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int baseline = win->y + uy + font_manager_get_font_ascent_scaled(font, scale) - 2;
|
||||
int cur_x = win->x + ux;
|
||||
const char *s = kernel_str;
|
||||
int start_x = cur_x;
|
||||
while (*s) {
|
||||
extern void font_manager_render_char_sloped(ttf_font_t *font, int x, int y, uint32_t codepoint, uint32_t color, float scale, float slope, void (*put_pixel_fn)(int, int, uint32_t));
|
||||
uint32_t codepoint = utf8_decode(&s);
|
||||
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
if (codepoint == '\n') {
|
||||
cur_x = start_x;
|
||||
baseline += font_manager_get_font_line_height_scaled(font, scale);
|
||||
} else if (codepoint == '\t') {
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, ' ', scale) * 4;
|
||||
} else {
|
||||
font_manager_render_char_sloped(font, cur_x, baseline, codepoint, color, scale, slope, put_pixel);
|
||||
cur_x += font_manager_get_codepoint_width_scaled(font, codepoint, scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draw_string_scaled_sloped(win->x + ux, win->y + uy, kernel_str, color, scale, slope);
|
||||
@@ -743,7 +826,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
Window *win = (Window *)arg2;
|
||||
if (win) {
|
||||
extern void serial_write(const char *str);
|
||||
serial_write("Kernel: Setting window resizable to ");
|
||||
serial_write("[WM] Resizable: ");
|
||||
serial_write(arg3 ? "true\n" : "false\n");
|
||||
win->resizable = (arg3 != 0);
|
||||
}
|
||||
@@ -852,33 +935,35 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
const char *mode = (const char *)arg3;
|
||||
if (!path || !mode) return -1;
|
||||
|
||||
FAT32_FileHandle *fh = fat32_open(path, mode);
|
||||
if (!fh) return -1;
|
||||
// vfs_open now handles normalization internally with process_get_current()
|
||||
// but let's be explicit if we can.
|
||||
vfs_file_t *vf = vfs_open(path, mode);
|
||||
if (!vf) return -1;
|
||||
|
||||
for (int i = 0; i < MAX_PROCESS_FDS; i++) {
|
||||
if (proc->fds[i] == NULL) {
|
||||
proc->fds[i] = fh;
|
||||
proc->fds[i] = vf;
|
||||
return (uint64_t)i;
|
||||
}
|
||||
}
|
||||
fat32_close(fh);
|
||||
vfs_close(vf);
|
||||
return -1;
|
||||
} else if (cmd == FS_CMD_READ) {
|
||||
int fd = (int)arg2;
|
||||
void *buf = (void *)arg3;
|
||||
uint32_t len = (uint32_t)arg4;
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
return (uint64_t)fat32_read((FAT32_FileHandle*)proc->fds[fd], buf, (int)len);
|
||||
return (uint64_t)vfs_read((vfs_file_t*)proc->fds[fd], buf, (int)len);
|
||||
} else if (cmd == FS_CMD_WRITE) {
|
||||
int fd = (int)arg2;
|
||||
const void *buf = (const void *)arg3;
|
||||
uint32_t len = (uint32_t)arg4;
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
return (uint64_t)fat32_write((FAT32_FileHandle*)proc->fds[fd], buf, (int)len);
|
||||
return (uint64_t)vfs_write((vfs_file_t*)proc->fds[fd], buf, (int)len);
|
||||
} else if (cmd == FS_CMD_CLOSE) {
|
||||
int fd = (int)arg2;
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
fat32_close((FAT32_FileHandle*)proc->fds[fd]);
|
||||
vfs_close((vfs_file_t*)proc->fds[fd]);
|
||||
proc->fds[fd] = NULL;
|
||||
return 0;
|
||||
} else if (cmd == FS_CMD_SEEK) {
|
||||
@@ -886,49 +971,107 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
int offset = (int)arg3;
|
||||
int whence = (int)arg4; // 0=SET, 1=CUR, 2=END
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
return (uint64_t)fat32_seek((FAT32_FileHandle*)proc->fds[fd], offset, whence);
|
||||
return (uint64_t)vfs_seek((vfs_file_t*)proc->fds[fd], offset, whence);
|
||||
} else if (cmd == FS_CMD_TELL) {
|
||||
int fd = (int)arg2;
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
return (uint64_t)((FAT32_FileHandle*)proc->fds[fd])->position;
|
||||
return (uint64_t)vfs_file_position((vfs_file_t*)proc->fds[fd]);
|
||||
} else if (cmd == FS_CMD_SIZE) {
|
||||
int fd = (int)arg2;
|
||||
if (fd < 0 || fd >= MAX_PROCESS_FDS || !proc->fds[fd]) return -1;
|
||||
return (uint64_t)((FAT32_FileHandle*)proc->fds[fd])->size;
|
||||
}
|
||||
else if (cmd == FS_CMD_LIST) {
|
||||
return (uint64_t)vfs_file_size((vfs_file_t*)proc->fds[fd]);
|
||||
} else if (cmd == FS_CMD_GETCWD) {
|
||||
char *buf = (char *)arg2;
|
||||
int size = (int)arg3;
|
||||
if (!buf || size <= 0) return -1;
|
||||
int len = (int)k_strlen(proc->cwd);
|
||||
if (len >= size) return -1;
|
||||
k_strcpy(buf, proc->cwd);
|
||||
return (uint64_t)len;
|
||||
} else if (cmd == FS_CMD_CHDIR) {
|
||||
const char *path = (const char *)arg2;
|
||||
FAT32_FileInfo *entries = (FAT32_FileInfo *)arg3;
|
||||
if (!path) return -1;
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path(proc->cwd, path, normalized);
|
||||
if (vfs_is_directory(normalized)) {
|
||||
k_strcpy(proc->cwd, normalized);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
} else if (cmd == FS_CMD_LIST) {
|
||||
const char *path = (const char *)arg2;
|
||||
FAT32_FileInfo *u_entries = (FAT32_FileInfo *)arg3;
|
||||
int max_entries = (int)arg4;
|
||||
if (!path || !entries) return -1;
|
||||
return (uint64_t)fat32_list_directory(path, entries, max_entries);
|
||||
if (!path || !u_entries) return -1;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path(proc->cwd, path, normalized);
|
||||
|
||||
// Safety cap for kernel allocation
|
||||
if (max_entries > 256) max_entries = 256;
|
||||
if (max_entries <= 0) return 0;
|
||||
|
||||
vfs_dirent_t *v_entries = (vfs_dirent_t *)kmalloc(sizeof(vfs_dirent_t) * max_entries);
|
||||
if (!v_entries) return -1;
|
||||
|
||||
int count = vfs_list_directory(normalized, v_entries, max_entries);
|
||||
if (count > 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Direct copy as layouts are now aligned
|
||||
k_strcpy(u_entries[i].name, v_entries[i].name);
|
||||
u_entries[i].size = v_entries[i].size;
|
||||
u_entries[i].is_directory = v_entries[i].is_directory;
|
||||
u_entries[i].start_cluster = v_entries[i].start_cluster;
|
||||
u_entries[i].write_date = v_entries[i].write_date;
|
||||
u_entries[i].write_time = v_entries[i].write_time;
|
||||
}
|
||||
}
|
||||
kfree(v_entries);
|
||||
return (uint64_t)count;
|
||||
} else if (cmd == FS_CMD_DELETE) {
|
||||
const char *path = (const char *)arg2;
|
||||
if (!path) return -1;
|
||||
return fat32_delete(path) ? 0 : -1;
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path(proc->cwd, path, normalized);
|
||||
return vfs_delete(normalized) ? 0 : -1;
|
||||
} else if (cmd == FS_CMD_GET_INFO) {
|
||||
const char *path = (const char *)arg2;
|
||||
FAT32_FileInfo *info = (FAT32_FileInfo *)arg3;
|
||||
if (!path || !info) return -1;
|
||||
extern int fat32_get_info(const char *path, FAT32_FileInfo *info);
|
||||
return (uint64_t)fat32_get_info(path, info);
|
||||
FAT32_FileInfo *u_info = (FAT32_FileInfo *)arg3;
|
||||
if (!path || !u_info) return -1;
|
||||
|
||||
char normalized[VFS_MAX_PATH];
|
||||
vfs_normalize_path(proc->cwd, path, normalized);
|
||||
|
||||
vfs_dirent_t v_info;
|
||||
int res = vfs_get_info(normalized, &v_info);
|
||||
if (res == 0) {
|
||||
k_strcpy(u_info->name, v_info.name);
|
||||
u_info->size = v_info.size;
|
||||
u_info->is_directory = v_info.is_directory;
|
||||
u_info->start_cluster = v_info.start_cluster;
|
||||
u_info->write_date = v_info.write_date;
|
||||
u_info->write_time = v_info.write_time;
|
||||
}
|
||||
return (uint64_t)res;
|
||||
} else if (cmd == FS_CMD_MKDIR) {
|
||||
const char *path = (const char *)arg2;
|
||||
if (!path) return -1;
|
||||
return fat32_mkdir(path) ? 0 : -1;
|
||||
return vfs_mkdir(path) ? 0 : -1;
|
||||
} else if (cmd == FS_CMD_EXISTS) {
|
||||
const char *path = (const char *)arg2;
|
||||
if (!path) return 0;
|
||||
return fat32_exists(path) ? 1 : 0;
|
||||
return vfs_exists(path) ? 1 : 0;
|
||||
} else if (cmd == FS_CMD_GETCWD) {
|
||||
char *buf = (char *)arg2;
|
||||
int size = (int)arg3;
|
||||
if (!buf) return -1;
|
||||
extern void fat32_get_current_dir(char *buf, int size);
|
||||
fat32_get_current_dir(buf, size);
|
||||
return 0;
|
||||
} else if (cmd == FS_CMD_CHDIR) {
|
||||
const char *path = (const char *)arg2;
|
||||
if (!path) return -1;
|
||||
extern bool fat32_chdir(const char *path);
|
||||
return fat32_chdir(path) ? 0 : -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -1055,15 +1198,6 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
extern void k_beep(int freq, int ms);
|
||||
k_beep(freq, ms);
|
||||
return 0;
|
||||
} else if (cmd == 15) { // SYSTEM_CMD_MEMINFO
|
||||
uint64_t *out = (uint64_t *)arg2;
|
||||
if (!out) return -1;
|
||||
MemStats stats = memory_get_stats();
|
||||
out[0] = stats.total_memory;
|
||||
out[1] = stats.used_memory;
|
||||
return 0;
|
||||
} else if (cmd == 16) { // SYSTEM_CMD_UPTIME
|
||||
return wm_get_ticks();
|
||||
} else if (cmd == 17) { // SYSTEM_CMD_PCI_LIST
|
||||
typedef struct {
|
||||
uint16_t vendor;
|
||||
@@ -1144,6 +1278,35 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
return cmd_get_config_value(key);
|
||||
} else if (cmd == 29) { // SYSTEM_CMD_SET_TEXT_COLOR
|
||||
uint32_t color = (uint32_t)arg2;
|
||||
if (proc->is_terminal_proc && proc->tty_id >= 0) {
|
||||
char seq[32];
|
||||
int pos = 0;
|
||||
int r = (color >> 16) & 0xFF;
|
||||
int g = (color >> 8) & 0xFF;
|
||||
int b = color & 0xFF;
|
||||
|
||||
seq[pos++] = 0x1B;
|
||||
seq[pos++] = '[';
|
||||
seq[pos++] = '3';
|
||||
seq[pos++] = '8';
|
||||
seq[pos++] = ';';
|
||||
seq[pos++] = '2';
|
||||
seq[pos++] = ';';
|
||||
|
||||
char num[8];
|
||||
k_itoa(r, num);
|
||||
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
|
||||
seq[pos++] = ';';
|
||||
k_itoa(g, num);
|
||||
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
|
||||
seq[pos++] = ';';
|
||||
k_itoa(b, num);
|
||||
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
|
||||
seq[pos++] = 'm';
|
||||
|
||||
tty_write_output(proc->tty_id, seq, (size_t)pos);
|
||||
return 0;
|
||||
}
|
||||
cmd_set_current_color(color);
|
||||
return 0;
|
||||
} else if (cmd == 31) { // SYSTEM_CMD_SET_WALLPAPER_PATH
|
||||
@@ -1224,54 +1387,7 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
size_t max_len = (size_t)arg3;
|
||||
extern int network_tcp_recv_nb(void *buf, size_t max_len);
|
||||
return (uint64_t)network_tcp_recv_nb(buf, max_len);
|
||||
} else if (cmd == SYSTEM_CMD_PROCESS_LIST) {
|
||||
ProcessInfo *out = (ProcessInfo *)arg2;
|
||||
int max_procs = (int)arg3;
|
||||
if (!out) return 0;
|
||||
|
||||
extern process_t processes[];
|
||||
|
||||
// Dynamically calculate kernel usage as: Total System Used - User Process Sum
|
||||
MemStats stats = memory_get_stats();
|
||||
size_t total_used = stats.used_memory;
|
||||
size_t user_used = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].is_user) {
|
||||
user_used += processes[i].used_memory;
|
||||
}
|
||||
}
|
||||
if (total_used > user_used) processes[0].used_memory = total_used - user_used;
|
||||
else processes[0].used_memory = 0;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (processes[i].pid != 0xFFFFFFFF && (processes[i].is_user || processes[i].pid == 0)) {
|
||||
out[count].pid = processes[i].pid;
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy(out[count].name, processes[i].name, 64);
|
||||
|
||||
if (processes[i].pid == 0) {
|
||||
out[count].name[0] = 'k'; out[count].name[1] = 'e'; out[count].name[2] = 'r';
|
||||
out[count].name[3] = 'n'; out[count].name[4] = 'e'; out[count].name[5] = 'l';
|
||||
out[count].name[6] = '\0';
|
||||
}
|
||||
|
||||
out[count].ticks = processes[i].ticks;
|
||||
out[count].used_memory = processes[i].used_memory;
|
||||
|
||||
count++;
|
||||
if (count >= max_procs) break;
|
||||
}
|
||||
}
|
||||
return (uint64_t)count;
|
||||
} else if (cmd == SYSTEM_CMD_GET_CPU_MODEL) {
|
||||
char *user_buf = (char *)arg2;
|
||||
if (!user_buf) return -1;
|
||||
char model[64];
|
||||
platform_get_cpu_model(model);
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
mem_memcpy(user_buf, model, 49);
|
||||
return 0;
|
||||
return -1;
|
||||
} else if (cmd == 47) { // SYSTEM_CMD_SET_RESOLUTION
|
||||
uint16_t req_w = (uint16_t)arg2;
|
||||
uint16_t req_h = (uint16_t)arg3;
|
||||
@@ -1310,12 +1426,86 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
} else if (cmd == 49) { // SYSTEM_CMD_GET_OS_INFO
|
||||
os_info_t *info = (os_info_t *)arg2;
|
||||
if (!info) return -1;
|
||||
extern void get_os_info(os_info_t *info);
|
||||
get_os_info(info);
|
||||
return -1;
|
||||
} else if (cmd == 60) { // SYSTEM_CMD_TTY_CREATE
|
||||
return tty_create();
|
||||
} else if (cmd == 61) { // SYSTEM_CMD_TTY_READ_OUT
|
||||
int tty_id = (int)arg2;
|
||||
char *buf = (char *)arg3;
|
||||
size_t len = (size_t)arg4;
|
||||
if (!buf || len == 0) return 0;
|
||||
return tty_read_output(tty_id, buf, len);
|
||||
} else if (cmd == 62) { // SYSTEM_CMD_TTY_WRITE_IN
|
||||
int tty_id = (int)arg2;
|
||||
const char *buf = (const char *)arg3;
|
||||
size_t len = (size_t)arg4;
|
||||
if (!buf || len == 0) return 0;
|
||||
return tty_write_input(tty_id, buf, len);
|
||||
} else if (cmd == 63) { // SYSTEM_CMD_TTY_READ_IN
|
||||
char *buf = (char *)arg2;
|
||||
size_t len = (size_t)arg3;
|
||||
if (!buf || len == 0) return 0;
|
||||
if (proc->tty_id < 0) return 0;
|
||||
return tty_read_input(proc->tty_id, buf, len);
|
||||
} else if (cmd == 65) { // SYSTEM_CMD_TTY_SET_FG
|
||||
int tty_id = (int)arg2;
|
||||
int pid = (int)arg3;
|
||||
return tty_set_foreground(tty_id, pid);
|
||||
} else if (cmd == 66) { // SYSTEM_CMD_TTY_GET_FG
|
||||
int tty_id = (int)arg2;
|
||||
return tty_get_foreground(tty_id);
|
||||
} else if (cmd == 67) { // SYSTEM_CMD_TTY_KILL_FG
|
||||
int tty_id = (int)arg2;
|
||||
int pid = tty_get_foreground(tty_id);
|
||||
if (pid <= 0) return 0;
|
||||
process_t *target = process_get_by_pid((uint32_t)pid);
|
||||
if (target) process_terminate(target);
|
||||
tty_set_foreground(tty_id, 0);
|
||||
return 0;
|
||||
} else if (cmd == 68) {
|
||||
int tty_id = (int)arg2;
|
||||
process_kill_by_tty(tty_id);
|
||||
tty_set_foreground(tty_id, 0);
|
||||
return 0;
|
||||
} else if (cmd == 69) {
|
||||
int tty_id = (int)arg2;
|
||||
return tty_destroy(tty_id);
|
||||
} else if (cmd == 64) {
|
||||
const char *user_path = (const char *)arg2;
|
||||
const char *user_args = (const char *)arg3;
|
||||
uint64_t flags = arg4;
|
||||
int tty_id = (int)arg5;
|
||||
|
||||
if (!user_path) return -1;
|
||||
|
||||
char path_buf[256];
|
||||
int pi = 0;
|
||||
while (pi < 255 && user_path[pi]) {
|
||||
path_buf[pi] = user_path[pi];
|
||||
pi++;
|
||||
}
|
||||
path_buf[pi] = 0;
|
||||
|
||||
char args_buf[512];
|
||||
const char *args_ptr = NULL;
|
||||
if (user_args) {
|
||||
int ai = 0;
|
||||
while (ai < 511 && user_args[ai]) {
|
||||
args_buf[ai] = user_args[ai];
|
||||
ai++;
|
||||
}
|
||||
args_buf[ai] = 0;
|
||||
args_ptr = args_buf;
|
||||
}
|
||||
|
||||
bool terminal_proc = (flags & SPAWN_FLAG_TERMINAL) != 0;
|
||||
int effective_tty = -1;
|
||||
if (flags & SPAWN_FLAG_TTY_ID) effective_tty = tty_id;
|
||||
else if (flags & SPAWN_FLAG_INHERIT_TTY) effective_tty = proc ? proc->tty_id : -1;
|
||||
|
||||
process_t *child = process_create_elf(path_buf, args_ptr, terminal_proc, effective_tty);
|
||||
if (!child) return -1;
|
||||
return (uint64_t)child->pid;
|
||||
} else if (cmd == SYSTEM_CMD_PARALLEL_RUN) {
|
||||
void (*user_fn)(void*) = (void (*)(void*))arg2;
|
||||
void **args = (void **)arg3;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct Window Window;
|
||||
typedef struct registers_t registers_t;
|
||||
|
||||
@@ -53,11 +52,8 @@ typedef struct {
|
||||
#define SYSTEM_CMD_SET_RAW_MODE 41
|
||||
#define SYSTEM_CMD_TCP_RECV_NB 42
|
||||
#define SYSTEM_CMD_YIELD 43
|
||||
#define SYSTEM_CMD_PROCESS_LIST 44
|
||||
#define SYSTEM_CMD_GET_CPU_MODEL 45
|
||||
#define SYSTEM_CMD_SLEEP 46
|
||||
#define SYSTEM_CMD_SET_RESOLUTION 47
|
||||
#define SYSTEM_CMD_GET_OS_INFO 49
|
||||
#define SYSTEM_CMD_PARALLEL_RUN 50
|
||||
|
||||
void syscall_init(void);
|
||||
|
||||
365
src/sys/sysfs_init.c
Normal file
365
src/sys/sysfs_init.c
Normal file
@@ -0,0 +1,365 @@
|
||||
#include "kernel_subsystem.h"
|
||||
#include "smp.h"
|
||||
#include "pci.h"
|
||||
#include "memory_manager.h"
|
||||
#include "module_manager.h"
|
||||
#include "io.h"
|
||||
#include "core/kutils.h"
|
||||
#include "wm/graphics.h"
|
||||
#include "core/platform.h"
|
||||
#include "dev/disk.h"
|
||||
|
||||
// --- Helper: itoa ---
|
||||
static void sys_itoa(int n, char *s) {
|
||||
k_itoa(n, s);
|
||||
}
|
||||
|
||||
// --- Graphics Implementation ---
|
||||
static int read_gfx_drm(char *buf, int size, int offset) {
|
||||
char out[512];
|
||||
k_memset(out, 0, 512);
|
||||
k_strcpy(out, "Driver: Simple Framebuffer\n");
|
||||
k_strcpy(out + k_strlen(out), "Resolution: ");
|
||||
char s[32]; k_itoa(get_screen_width(), s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "x");
|
||||
k_itoa(get_screen_height(), s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "\nDepth: ");
|
||||
k_itoa(graphics_get_fb_bpp(), s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), " bpp\nAddress: 0x");
|
||||
k_itoa_hex(graphics_get_fb_addr(), s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- Memory Tracking Implementation ---
|
||||
static int read_mem_tracking(char *buf, int size, int offset) {
|
||||
MemStats stats = memory_get_stats();
|
||||
char out[1024];
|
||||
k_memset(out, 0, 1024);
|
||||
|
||||
k_strcpy(out, "--- Kernel Heap Tracking ---\n");
|
||||
k_strcpy(out + k_strlen(out), "Allocated Blocks: ");
|
||||
char s[32]; k_itoa(stats.allocated_blocks, s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "\nFragmentation: ");
|
||||
k_itoa(stats.fragmentation_percent, s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "%\n");
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- Module Implementation ---
|
||||
static int read_sys_modules(char *buf, int size, int offset) {
|
||||
int count = module_manager_get_count();
|
||||
char out[2048] = "Loaded Modules:\n";
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
kernel_module_t *mod = module_manager_get_index(i);
|
||||
k_strcpy(out + k_strlen(out), " - ");
|
||||
k_strcpy(out + k_strlen(out), mod->name);
|
||||
k_strcpy(out + k_strlen(out), " (");
|
||||
char sz_s[16]; k_itoa(mod->size / 1024, sz_s);
|
||||
k_strcpy(out + k_strlen(out), sz_s);
|
||||
k_strcpy(out + k_strlen(out), " KB)\n");
|
||||
}
|
||||
|
||||
int len = k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- PCI Bus Implementation ---
|
||||
static int read_pci_bus(char *buf, int size, int offset) {
|
||||
pci_device_t devices[64];
|
||||
int count = pci_enumerate_devices(devices, 64);
|
||||
|
||||
char out[4096];
|
||||
k_memset(out, 0, 4096);
|
||||
k_strcpy(out, "PCI Bus Devices:\n");
|
||||
for (int i = 0; i < count; i++) {
|
||||
char line[128];
|
||||
k_strcpy(line, " [");
|
||||
char b_s[8]; k_itoa(devices[i].bus, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), ":");
|
||||
k_itoa(devices[i].device, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), ":");
|
||||
k_itoa(devices[i].function, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), "] Vendor:");
|
||||
k_itoa_hex(devices[i].vendor_id, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), " Device:");
|
||||
k_itoa_hex(devices[i].device_id, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), " Class:");
|
||||
k_itoa_hex(devices[i].class_code, b_s);
|
||||
k_strcpy(line + k_strlen(line), b_s);
|
||||
k_strcpy(line + k_strlen(line), "\n");
|
||||
|
||||
if (k_strlen(out) + k_strlen(line) < 4095) {
|
||||
k_strcpy(out + k_strlen(out), line);
|
||||
}
|
||||
}
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- CPU System Implementation ---
|
||||
static int read_cpu_info(char *buf, int size, int offset) {
|
||||
char *out = (char*)kmalloc(16384);
|
||||
if (!out) return 0;
|
||||
out[0] = 0;
|
||||
|
||||
char vendor[16];
|
||||
char model[64];
|
||||
char flags[1024];
|
||||
cpu_info_t info;
|
||||
|
||||
platform_get_cpu_vendor(vendor);
|
||||
platform_get_cpu_model(model);
|
||||
platform_get_cpu_info(&info);
|
||||
platform_get_cpu_flags(flags);
|
||||
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
|
||||
for (uint32_t i = 0; i < cpu_count; i++) {
|
||||
char c_s[32];
|
||||
|
||||
k_strcpy(out + k_strlen(out), "processor\t: ");
|
||||
k_itoa(i, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "vendor_id\t: ");
|
||||
k_strcpy(out + k_strlen(out), vendor);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpu family\t: ");
|
||||
k_itoa(info.family, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "model\t\t: ");
|
||||
k_itoa(info.model, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "model name\t: ");
|
||||
k_strcpy(out + k_strlen(out), model);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "stepping\t: ");
|
||||
k_itoa(info.stepping, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "microcode\t: 0x");
|
||||
char hex[16];
|
||||
int temp = info.microcode;
|
||||
int hex_pos = 0;
|
||||
for (int j = 7; j >= 0; j--) {
|
||||
int digit = (temp >> (j * 4)) & 0xF;
|
||||
hex[hex_pos++] = digit < 10 ? '0' + digit : 'a' + (digit - 10);
|
||||
}
|
||||
hex[hex_pos] = '\0';
|
||||
k_strcpy(out + k_strlen(out), hex);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cache size\t: ");
|
||||
k_itoa(info.cache_size, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), " KB\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "physical id\t: 0\n");
|
||||
k_strcpy(out + k_strlen(out), "siblings\t: ");
|
||||
k_itoa(cpu_count, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "core id\t\t: ");
|
||||
k_itoa(i, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpu cores\t: ");
|
||||
k_itoa(cpu_count, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "apicid\t\t: ");
|
||||
k_itoa(i, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "initial apicid\t: ");
|
||||
k_itoa(i, c_s);
|
||||
k_strcpy(out + k_strlen(out), c_s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "fpu\t\t: yes\n");
|
||||
k_strcpy(out + k_strlen(out), "fpu_exception\t: yes\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "cpuid level\t: 13\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "wp\t\t: yes\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "flags\t\t: ");
|
||||
k_strcpy(out + k_strlen(out), flags);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
k_strcpy(out + k_strlen(out), "bugs\t\t: \n");
|
||||
k_strcpy(out + k_strlen(out), "bogomips\t: 4800.00\n");
|
||||
|
||||
if (i < cpu_count - 1) {
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) { kfree(out); return 0; }
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
kfree(out);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- Devices Implementation ---
|
||||
static int read_sys_devices(char *buf, int size, int offset) {
|
||||
char out[2048];
|
||||
k_memset(out, 0, 2048);
|
||||
|
||||
extern int disk_get_count(void);
|
||||
extern Disk* disk_get_by_index(int index);
|
||||
|
||||
int dcount = disk_get_count();
|
||||
k_strcpy(out, "Block Devices:\n");
|
||||
for (int i = 0; i < dcount; i++) {
|
||||
Disk *d = disk_get_by_index(i);
|
||||
if (d && !d->is_partition) {
|
||||
k_strcpy(out + k_strlen(out), " ");
|
||||
k_strcpy(out + k_strlen(out), d->devname);
|
||||
k_strcpy(out + k_strlen(out), " - ");
|
||||
k_strcpy(out + k_strlen(out), d->label);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
k_strcpy(out + k_strlen(out), "\nCharacter Devices:\n");
|
||||
k_strcpy(out + k_strlen(out), " console - System console\n");
|
||||
k_strcpy(out + k_strlen(out), " tty - Terminal devices\n");
|
||||
k_strcpy(out + k_strlen(out), " psmouse - Mouse input\n");
|
||||
k_strcpy(out + k_strlen(out), " keyboard - Keyboard input\n");
|
||||
k_strcpy(out + k_strlen(out), " framebuffer - Framebuffer device\n");
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- Class Implementation ---
|
||||
static int read_sys_class(char *buf, int size, int offset) {
|
||||
char out[1024];
|
||||
k_memset(out, 0, 1024);
|
||||
|
||||
k_strcpy(out, "Classes:\n");
|
||||
k_strcpy(out + k_strlen(out), " block - Block device class\n");
|
||||
k_strcpy(out + k_strlen(out), " input - Input device class\n");
|
||||
k_strcpy(out + k_strlen(out), " tty - TTY device class\n");
|
||||
k_strcpy(out + k_strlen(out), " sound - Sound device class\n");
|
||||
k_strcpy(out + k_strlen(out), " video - Video device class\n");
|
||||
k_strcpy(out + k_strlen(out), " net - Network device class\n");
|
||||
|
||||
int len = (int)k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
// --- GPIO Implementation ---
|
||||
static int read_gpio_debug(char *buf, int size, int offset) {
|
||||
uint8_t p64 = inb(0x64);
|
||||
char out[64] = "Port 0x64 Status: ";
|
||||
char s[16]; k_itoa(p64, s);
|
||||
k_strcpy(out + k_strlen(out), s);
|
||||
k_strcpy(out + k_strlen(out), "\n");
|
||||
|
||||
int len = k_strlen(out);
|
||||
if (offset >= len) return 0;
|
||||
int to_copy = len - offset;
|
||||
if (to_copy > size) to_copy = size;
|
||||
k_memcpy(buf, out + offset, to_copy);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
void sysfs_init_subsystems(void) {
|
||||
kernel_subsystem_t *kernel, *devices, *bus, *class, *debug, *mem_debug;
|
||||
|
||||
subsystem_register("kernel", &kernel);
|
||||
subsystem_register("devices", &devices);
|
||||
subsystem_register("bus", &bus);
|
||||
subsystem_register("class", &class);
|
||||
subsystem_register("kernel/debug", &debug);
|
||||
|
||||
// Devices info
|
||||
subsystem_add_file(devices, "list", read_sys_devices, NULL);
|
||||
|
||||
// Class info
|
||||
subsystem_add_file(class, "list", read_sys_class, NULL);
|
||||
|
||||
// CPU info
|
||||
subsystem_add_file(kernel, "cpuinfo", read_cpu_info, NULL);
|
||||
|
||||
// Bus info
|
||||
kernel_subsystem_t *pci_bus;
|
||||
subsystem_register("bus/pci", &pci_bus);
|
||||
subsystem_add_file(pci_bus, "devices", read_pci_bus, NULL);
|
||||
|
||||
// Module info
|
||||
kernel_subsystem_t *modules_sub;
|
||||
subsystem_register("module", &modules_sub);
|
||||
subsystem_add_file(modules_sub, "loaded", read_sys_modules, NULL);
|
||||
|
||||
// Memory Tracking
|
||||
subsystem_register("kernel/debug/memory", &mem_debug);
|
||||
subsystem_add_file(mem_debug, "tracking", read_mem_tracking, NULL);
|
||||
|
||||
// Graphics DRM
|
||||
kernel_subsystem_t *gfx_debug;
|
||||
subsystem_register("kernel/debug/graphics", &gfx_debug);
|
||||
subsystem_add_file(gfx_debug, "drm", read_gfx_drm, NULL);
|
||||
|
||||
// GPIO
|
||||
subsystem_add_file(debug, "gpio", read_gpio_debug, NULL);
|
||||
}
|
||||
158
src/sys/tty.c
Normal file
158
src/sys/tty.c
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "tty.h"
|
||||
#include "spinlock.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TTY_MAX 8
|
||||
#define TTY_OUT_SIZE 16384
|
||||
#define TTY_IN_SIZE 4096
|
||||
|
||||
typedef struct {
|
||||
bool used;
|
||||
int id;
|
||||
char out_buf[TTY_OUT_SIZE];
|
||||
uint32_t out_head;
|
||||
uint32_t out_tail;
|
||||
char in_buf[TTY_IN_SIZE];
|
||||
uint32_t in_head;
|
||||
uint32_t in_tail;
|
||||
int fg_pid;
|
||||
spinlock_t lock;
|
||||
} tty_t;
|
||||
|
||||
static tty_t ttys[TTY_MAX] = {0};
|
||||
|
||||
extern void mem_memset(void *dest, int val, size_t len);
|
||||
|
||||
static tty_t *tty_get(int tty_id) {
|
||||
if (tty_id < 0 || tty_id >= TTY_MAX) return NULL;
|
||||
if (!ttys[tty_id].used) return NULL;
|
||||
return &ttys[tty_id];
|
||||
}
|
||||
|
||||
int tty_create(void) {
|
||||
for (int i = 0; i < TTY_MAX; i++) {
|
||||
if (!ttys[i].used) {
|
||||
ttys[i].used = true;
|
||||
ttys[i].id = i;
|
||||
ttys[i].out_head = 0;
|
||||
ttys[i].out_tail = 0;
|
||||
ttys[i].in_head = 0;
|
||||
ttys[i].in_tail = 0;
|
||||
ttys[i].fg_pid = -1;
|
||||
ttys[i].lock = SPINLOCK_INIT;
|
||||
mem_memset(ttys[i].out_buf, 0, sizeof(ttys[i].out_buf));
|
||||
mem_memset(ttys[i].in_buf, 0, sizeof(ttys[i].in_buf));
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int tty_destroy(int tty_id) {
|
||||
if (tty_id < 0 || tty_id >= TTY_MAX) return -1;
|
||||
tty_t *tty = &ttys[tty_id];
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
if (!tty->used) {
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tty->used = false;
|
||||
tty->id = -1;
|
||||
tty->out_head = 0;
|
||||
tty->out_tail = 0;
|
||||
tty->in_head = 0;
|
||||
tty->in_tail = 0;
|
||||
tty->fg_pid = -1;
|
||||
mem_memset(tty->out_buf, 0, sizeof(tty->out_buf));
|
||||
mem_memset(tty->in_buf, 0, sizeof(tty->in_buf));
|
||||
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tty_write_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, const char *data, size_t len) {
|
||||
int written = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint32_t next = (*head + 1) % size;
|
||||
if (next == *tail) break;
|
||||
buf[*head] = data[i];
|
||||
*head = next;
|
||||
written++;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
static int tty_read_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, char *out, size_t max_len) {
|
||||
int read = 0;
|
||||
while (*tail != *head && (size_t)read < max_len) {
|
||||
out[read++] = buf[*tail];
|
||||
*tail = (*tail + 1) % size;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
int tty_write_output(int tty_id, const char *data, size_t len) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty || !data || len == 0) return 0;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
int written = tty_write_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, data, len);
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return written;
|
||||
}
|
||||
|
||||
int tty_read_output(int tty_id, char *buf, size_t max_len) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty || !buf || max_len == 0) return 0;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
int read = tty_read_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, buf, max_len);
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return read;
|
||||
}
|
||||
|
||||
int tty_write_input(int tty_id, const char *data, size_t len) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty || !data || len == 0) return 0;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
int written = tty_write_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, data, len);
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return written;
|
||||
}
|
||||
|
||||
int tty_read_input(int tty_id, char *buf, size_t max_len) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty || !buf || max_len == 0) return 0;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
int read = tty_read_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, buf, max_len);
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return read;
|
||||
}
|
||||
|
||||
int tty_set_foreground(int tty_id, int pid) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty) return -1;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
tty->fg_pid = pid;
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tty_get_foreground(int tty_id) {
|
||||
tty_t *tty = tty_get(tty_id);
|
||||
if (!tty) return -1;
|
||||
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
|
||||
int pid = tty->fg_pid;
|
||||
spinlock_release_irqrestore(&tty->lock, rflags);
|
||||
return pid;
|
||||
}
|
||||
18
src/sys/tty.h
Normal file
18
src/sys/tty.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 TTY_H
|
||||
#define TTY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int tty_create(void);
|
||||
int tty_destroy(int tty_id);
|
||||
int tty_write_output(int tty_id, const char *data, size_t len);
|
||||
int tty_read_output(int tty_id, char *buf, size_t max_len);
|
||||
int tty_write_input(int tty_id, const char *data, size_t len);
|
||||
int tty_read_input(int tty_id, char *buf, size_t max_len);
|
||||
int tty_set_foreground(int tty_id, int pid);
|
||||
int tty_get_foreground(int tty_id);
|
||||
|
||||
#endif
|
||||
1454
src/userland/cli/bsh.c
Normal file
1454
src/userland/cli/bsh.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
|
||||
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: cat <filename>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd = sys_open(argv[1], "r");
|
||||
if (fd < 0) {
|
||||
sys_set_text_color(error_color);
|
||||
printf("Error: Cannot open %s\n", argv[1]);
|
||||
sys_set_text_color(default_color);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
int bytes;
|
||||
while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) {
|
||||
sys_write(1, buffer, bytes);
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
typedef int bool;
|
||||
#define true 1
|
||||
#define false 0
|
||||
|
||||
void combine_path(char *dest, const char *path1, const char *path2) {
|
||||
int i = 0;
|
||||
while (path1[i]) {
|
||||
dest[i] = path1[i];
|
||||
i++;
|
||||
}
|
||||
if (i > 0 && dest[i-1] != '/') {
|
||||
dest[i++] = '/';
|
||||
}
|
||||
int j = 0;
|
||||
while (path2[j]) {
|
||||
dest[i++] = path2[j++];
|
||||
}
|
||||
dest[i] = 0;
|
||||
}
|
||||
|
||||
const char* get_basename(const char *path) {
|
||||
const char *last_slash = NULL;
|
||||
int len = 0;
|
||||
while (path[len]) {
|
||||
if (path[len] == '/') last_slash = path + len;
|
||||
len++;
|
||||
}
|
||||
|
||||
if (!last_slash) return path;
|
||||
|
||||
// If it ends with a slash, skip it and find the previous one
|
||||
if (last_slash[1] == '\0') {
|
||||
if (len <= 1) return path; // root "/"
|
||||
int i = len - 2;
|
||||
while (i >= 0 && path[i] != '/') i--;
|
||||
if (i < 0) return path;
|
||||
return path + i + 1;
|
||||
}
|
||||
|
||||
return last_slash + 1;
|
||||
}
|
||||
|
||||
void copy_recursive(const char *src, const char *dst) {
|
||||
FAT32_FileInfo info;
|
||||
if (sys_get_file_info(src, &info) < 0) {
|
||||
printf("Error: Cannot get info for %s\n", src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.is_directory) {
|
||||
if (sys_mkdir(dst) < 0) {
|
||||
// Might already exist
|
||||
}
|
||||
|
||||
FAT32_FileInfo entries[64];
|
||||
int count = sys_list(src, entries, 64);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
|
||||
|
||||
char sub_src[512], sub_dst[512];
|
||||
combine_path(sub_src, src, entries[i].name);
|
||||
combine_path(sub_dst, dst, entries[i].name);
|
||||
copy_recursive(sub_src, sub_dst);
|
||||
}
|
||||
} else {
|
||||
int fd_in = sys_open(src, "r");
|
||||
if (fd_in < 0) {
|
||||
printf("Error: Cannot open source %s\n", src);
|
||||
return;
|
||||
}
|
||||
int fd_out = sys_open(dst, "w");
|
||||
if (fd_out < 0) {
|
||||
printf("Error: Cannot create destination %s\n", dst);
|
||||
sys_close(fd_in);
|
||||
return;
|
||||
}
|
||||
char buffer[4096];
|
||||
int bytes;
|
||||
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
|
||||
sys_write_fs(fd_out, buffer, bytes);
|
||||
}
|
||||
sys_close(fd_in);
|
||||
sys_close(fd_out);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool recursive = false;
|
||||
char *src_path = NULL;
|
||||
char *dst_path = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-r") == 0) {
|
||||
recursive = true;
|
||||
} else if (!src_path) {
|
||||
src_path = argv[i];
|
||||
} else if (!dst_path) {
|
||||
dst_path = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!src_path || !dst_path) {
|
||||
printf("Usage: cp [-r] <source> <dest>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FAT32_FileInfo info_src;
|
||||
if (sys_get_file_info(src_path, &info_src) < 0) {
|
||||
printf("Error: Source %s does not exist\n", src_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (info_src.is_directory && !recursive) {
|
||||
printf("Error: %s is a directory (use -r to copy recursively)\n", src_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char actual_dst[512];
|
||||
FAT32_FileInfo info_dst;
|
||||
if (sys_get_file_info(dst_path, &info_dst) == 0 && info_dst.is_directory) {
|
||||
// If destination is a directory, copy INTO it
|
||||
const char *base = get_basename(src_path);
|
||||
// Clean up trailing slash from basename if any (get_basename handles it mostly)
|
||||
char clean_base[256];
|
||||
int k = 0;
|
||||
while (base[k] && base[k] != '/') {
|
||||
clean_base[k] = base[k];
|
||||
k++;
|
||||
}
|
||||
clean_base[k] = 0;
|
||||
combine_path(actual_dst, dst_path, clean_base);
|
||||
} else {
|
||||
strcpy(actual_dst, dst_path);
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
copy_recursive(src_path, actual_dst);
|
||||
} else {
|
||||
int fd_in = sys_open(src_path, "r");
|
||||
if (fd_in < 0) {
|
||||
printf("Error: Cannot open source %s\n", src_path);
|
||||
return 1;
|
||||
}
|
||||
int fd_out = sys_open(actual_dst, "w");
|
||||
if (fd_out < 0) {
|
||||
printf("Error: Cannot create destination %s\n", actual_dst);
|
||||
sys_close(fd_in);
|
||||
return 1;
|
||||
}
|
||||
char buffer[4096];
|
||||
int bytes;
|
||||
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
|
||||
sys_write_fs(fd_out, buffer, bytes);
|
||||
}
|
||||
sys_close(fd_in);
|
||||
sys_close(fd_out);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint64_t dir_color = sys_get_shell_config("dir_color");
|
||||
uint64_t file_color = sys_get_shell_config("file_color");
|
||||
uint64_t size_color = sys_get_shell_config("size_color");
|
||||
uint64_t error_color = sys_get_shell_config("error_color");
|
||||
uint64_t default_color = sys_get_shell_config("default_text_color");
|
||||
|
||||
char path[256];
|
||||
if (argc > 1) {
|
||||
strcpy(path, argv[1]);
|
||||
} else {
|
||||
if (!sys_getcwd(path, sizeof(path))) {
|
||||
strcpy(path, "/");
|
||||
}
|
||||
}
|
||||
|
||||
FAT32_FileInfo info;
|
||||
if (sys_get_file_info(path, &info) < 0) {
|
||||
sys_set_text_color(error_color);
|
||||
printf("Error: Path '%s' does not exist\n", path);
|
||||
sys_set_text_color(default_color);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!info.is_directory) {
|
||||
sys_set_text_color(file_color);
|
||||
printf("[FILE] %s", info.name);
|
||||
sys_set_text_color(size_color);
|
||||
printf(" (%d bytes)\n", info.size);
|
||||
sys_set_text_color(default_color);
|
||||
printf("\nTotal: 1 items\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
FAT32_FileInfo entries[128];
|
||||
int count = sys_list(path, entries, 128);
|
||||
|
||||
if (count < 0) {
|
||||
sys_set_text_color(error_color);
|
||||
printf("Error: Cannot list directory %s\n", path);
|
||||
sys_set_text_color(default_color);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (entries[i].is_directory) {
|
||||
sys_set_text_color(dir_color);
|
||||
printf("[DIR] %s\n", entries[i].name);
|
||||
} else {
|
||||
sys_set_text_color(file_color);
|
||||
printf("[FILE] %s", entries[i].name);
|
||||
sys_set_text_color(size_color);
|
||||
printf(" (%d bytes)\n", entries[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
sys_set_text_color(default_color);
|
||||
printf("\nTotal: %d items\n", count);
|
||||
return 0;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ int main(int argc, char **argv) {
|
||||
printf("Manual for: %s\n", argv[1]);
|
||||
printf("---------------------------\n");
|
||||
|
||||
strcpy(path, "A:/Library/man/");
|
||||
strcpy(path, "/Library/man/");
|
||||
strcat(path, argv[1]);
|
||||
strcat(path, ".txt");
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
|
||||
uint32_t success_color = (uint32_t)sys_get_shell_config("success_color");
|
||||
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: mkdir <dirname>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sys_mkdir(argv[1]) == 0) {
|
||||
sys_set_text_color(success_color);
|
||||
printf("Created directory: %s\n", argv[1]);
|
||||
} else {
|
||||
sys_set_text_color(error_color);
|
||||
printf("Error: Cannot create directory %s\n", argv[1]);
|
||||
sys_set_text_color(default_color);
|
||||
return 1;
|
||||
}
|
||||
sys_set_text_color(default_color);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
void combine_path(char *dest, const char *path1, const char *path2) {
|
||||
int i = 0;
|
||||
while (path1[i]) {
|
||||
dest[i] = path1[i];
|
||||
i++;
|
||||
}
|
||||
if (i > 0 && dest[i-1] != '/') {
|
||||
dest[i++] = '/';
|
||||
}
|
||||
int j = 0;
|
||||
while (path2[j]) {
|
||||
dest[i++] = path2[j++];
|
||||
}
|
||||
dest[i] = 0;
|
||||
}
|
||||
|
||||
const char* get_basename(const char *path) {
|
||||
const char *last_slash = NULL;
|
||||
int len = 0;
|
||||
while (path[len]) {
|
||||
if (path[len] == '/') last_slash = path + len;
|
||||
len++;
|
||||
}
|
||||
|
||||
if (!last_slash) return path;
|
||||
|
||||
if (last_slash[1] == '\0') {
|
||||
if (len <= 1) return path;
|
||||
int i = len - 2;
|
||||
while (i >= 0 && path[i] != '/') i--;
|
||||
if (i < 0) return path;
|
||||
return path + i + 1;
|
||||
}
|
||||
|
||||
return last_slash + 1;
|
||||
}
|
||||
|
||||
void copy_recursive(const char *src, const char *dst) {
|
||||
FAT32_FileInfo info;
|
||||
if (sys_get_file_info(src, &info) < 0) return;
|
||||
|
||||
if (info.is_directory) {
|
||||
sys_mkdir(dst);
|
||||
FAT32_FileInfo entries[64];
|
||||
int count = sys_list(src, entries, 64);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
|
||||
char sub_src[512], sub_dst[512];
|
||||
combine_path(sub_src, src, entries[i].name);
|
||||
combine_path(sub_dst, dst, entries[i].name);
|
||||
copy_recursive(sub_src, sub_dst);
|
||||
}
|
||||
} else {
|
||||
int fd_in = sys_open(src, "r");
|
||||
if (fd_in < 0) return;
|
||||
int fd_out = sys_open(dst, "w");
|
||||
if (fd_out < 0) { sys_close(fd_in); return; }
|
||||
char buffer[4096];
|
||||
int bytes;
|
||||
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
|
||||
sys_write_fs(fd_out, buffer, bytes);
|
||||
}
|
||||
sys_close(fd_in);
|
||||
sys_close(fd_out);
|
||||
}
|
||||
}
|
||||
|
||||
void delete_recursive(const char *path) {
|
||||
FAT32_FileInfo info;
|
||||
if (sys_get_file_info(path, &info) < 0) return;
|
||||
|
||||
if (info.is_directory) {
|
||||
FAT32_FileInfo entries[64];
|
||||
int count = sys_list(path, entries, 64);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
|
||||
char sub_path[512];
|
||||
combine_path(sub_path, path, entries[i].name);
|
||||
delete_recursive(sub_path);
|
||||
}
|
||||
sys_delete(path);
|
||||
} else {
|
||||
sys_delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
printf("Usage: mv <source> <dest>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *src_path = argv[1];
|
||||
char *dst_path = argv[2];
|
||||
|
||||
FAT32_FileInfo info_src;
|
||||
if (sys_get_file_info(src_path, &info_src) < 0) {
|
||||
printf("Error: Cannot open source %s\n", src_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char actual_dst[512];
|
||||
FAT32_FileInfo info_dst;
|
||||
if (sys_get_file_info(dst_path, &info_dst) == 0 && info_dst.is_directory) {
|
||||
const char *base = get_basename(src_path);
|
||||
char clean_base[256];
|
||||
int k = 0;
|
||||
while (base[k] && base[k] != '/') {
|
||||
clean_base[k] = base[k];
|
||||
k++;
|
||||
}
|
||||
clean_base[k] = 0;
|
||||
combine_path(actual_dst, dst_path, clean_base);
|
||||
} else {
|
||||
strcpy(actual_dst, dst_path);
|
||||
}
|
||||
|
||||
copy_recursive(src_path, actual_dst);
|
||||
delete_recursive(src_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
|
||||
uint32_t success_color = (uint32_t)sys_get_shell_config("success_color");
|
||||
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: rm <path>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Simple rm (no recursive support yet for simplicity, but can be added)
|
||||
if (sys_delete(argv[1]) == 0) {
|
||||
sys_set_text_color(success_color);
|
||||
printf("Deleted: %s\n", argv[1]);
|
||||
} else {
|
||||
sys_set_text_color(error_color);
|
||||
printf("Error: Cannot delete %s\n", argv[1]);
|
||||
sys_set_text_color(default_color);
|
||||
return 1;
|
||||
}
|
||||
sys_set_text_color(default_color);
|
||||
return 0;
|
||||
}
|
||||
@@ -9,15 +9,6 @@
|
||||
#define MAX_ASCII_WIDTH 80
|
||||
#define MAX_INFO_LINES 10
|
||||
|
||||
static char* strchr(const char *s, int c) {
|
||||
while (*s != (char)c) {
|
||||
if (!*s++) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return (char *)s;
|
||||
}
|
||||
|
||||
static 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];
|
||||
@@ -117,7 +108,7 @@ static char* trim(char *str) {
|
||||
}
|
||||
|
||||
static void set_config_defaults() {
|
||||
strcpy(config.ascii_art_file, "A:/Library/art/boredos.txt");
|
||||
strcpy(config.ascii_art_file, "/Library/art/boredos.txt");
|
||||
strcpy(config.user_host_string, "root@boredos");
|
||||
strcpy(config.separator, "------------");
|
||||
strcpy(config.os_label, "OS");
|
||||
@@ -160,7 +151,7 @@ static void parse_config(char* buffer) {
|
||||
|
||||
static void load_config() {
|
||||
set_config_defaults();
|
||||
int fd = sys_open("A:/Library/conf/sysfetch.cfg", "r");
|
||||
int fd = sys_open("/Library/conf/sysfetch.cfg", "r");
|
||||
if (fd < 0) return;
|
||||
|
||||
char *buffer = malloc(4096);
|
||||
@@ -237,52 +228,82 @@ int main(int argc, char **argv) {
|
||||
if (config.separator[0]) {
|
||||
strcpy(info_lines[info_line_count++], config.separator);
|
||||
}
|
||||
os_info_t os_info;
|
||||
sys_get_os_info(&os_info);
|
||||
// Helper for proc parsing
|
||||
auto int find_v(const char *b, const char *k) {
|
||||
char *p = (char*)b; int kl = strlen(k);
|
||||
while (*p) {
|
||||
if (memcmp(p, k, kl) == 0 && p[kl] == ':') {
|
||||
p += kl + 1; while (*p == ' ') p++; return atoi(p);
|
||||
}
|
||||
while (*p && *p != '\n') p++; if (*p == '\n') p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_v = sys_open("/proc/version", "r");
|
||||
char v_buf[512];
|
||||
if (fd_v >= 0) {
|
||||
int b = sys_read(fd_v, v_buf, 511);
|
||||
v_buf[b] = 0;
|
||||
sys_close(fd_v);
|
||||
} else strcpy(v_buf, "Unknown");
|
||||
|
||||
if (config.os_label[0]) {
|
||||
strcpy(info_lines[info_line_count], config.os_label);
|
||||
strcat(info_lines[info_line_count], ": ");
|
||||
strcat(info_lines[info_line_count], os_info.os_name);
|
||||
strcat(info_lines[info_line_count], " V");
|
||||
strcat(info_lines[info_line_count], os_info.os_version);
|
||||
strcat(info_lines[info_line_count], " '");
|
||||
strcat(info_lines[info_line_count], os_info.os_codename);
|
||||
strcat(info_lines[info_line_count], "'");
|
||||
// Parse "BoredOS [codename] Version X.Y.Z"
|
||||
strcat(info_lines[info_line_count], v_buf);
|
||||
// Truncate at newline
|
||||
char *nl = strchr(info_lines[info_line_count], '\n');
|
||||
if (nl) *nl = 0;
|
||||
info_line_count++;
|
||||
}
|
||||
if (config.kernel_label[0]) {
|
||||
strcpy(info_lines[info_line_count], config.kernel_label);
|
||||
strcat(info_lines[info_line_count], ": ");
|
||||
strcat(info_lines[info_line_count], os_info.kernel_name);
|
||||
strcat(info_lines[info_line_count], " V");
|
||||
strcat(info_lines[info_line_count], os_info.kernel_version);
|
||||
strcat(info_lines[info_line_count], " ");
|
||||
strcat(info_lines[info_line_count], os_info.build_arch);
|
||||
char *kstart = strchr(v_buf, '\n');
|
||||
if (kstart) {
|
||||
strcat(info_lines[info_line_count], kstart + 1);
|
||||
char *knext = strchr(info_lines[info_line_count], '\n');
|
||||
if (knext) *knext = 0;
|
||||
} else strcat(info_lines[info_line_count], "Unknown");
|
||||
info_line_count++;
|
||||
}
|
||||
if (config.uptime_label[0]) {
|
||||
uint64_t ticks = sys_system(16, 0, 0, 0, 0);
|
||||
int minutes = ticks / 3600; // 60Hz timer
|
||||
strcpy(info_lines[info_line_count], config.uptime_label);
|
||||
strcat(info_lines[info_line_count], ": ");
|
||||
itoa(minutes, temp_buf);
|
||||
strcat(info_lines[info_line_count], temp_buf);
|
||||
strcat(info_lines[info_line_count++], " mins");
|
||||
int fd_u = sys_open("/proc/uptime", "r");
|
||||
if (fd_u >= 0) {
|
||||
char u_buf[64];
|
||||
int b = sys_read(fd_u, u_buf, 63);
|
||||
u_buf[b] = 0;
|
||||
sys_close(fd_u);
|
||||
int sec = atoi(u_buf);
|
||||
int mins = sec / 60;
|
||||
strcpy(info_lines[info_line_count], config.uptime_label);
|
||||
strcat(info_lines[info_line_count], ": ");
|
||||
itoa(mins, temp_buf);
|
||||
strcat(info_lines[info_line_count], temp_buf);
|
||||
strcat(info_lines[info_line_count++], " mins");
|
||||
}
|
||||
}
|
||||
if (config.shell_label[0]) {
|
||||
strcpy(info_lines[info_line_count], config.shell_label);
|
||||
strcat(info_lines[info_line_count++], ": bsh");
|
||||
}
|
||||
if (config.memory_label[0]) {
|
||||
uint64_t mem[2];
|
||||
if (sys_system(15, (uint64_t)mem, 0, 0, 0) == 0) {
|
||||
int fd_m = sys_open("/proc/meminfo", "r");
|
||||
if (fd_m >= 0) {
|
||||
char m_buf[512];
|
||||
int b = sys_read(fd_m, m_buf, 511);
|
||||
m_buf[b] = 0;
|
||||
sys_close(fd_m);
|
||||
int total = find_v(m_buf, "MemTotal");
|
||||
int used = find_v(m_buf, "MemUsed");
|
||||
strcpy(info_lines[info_line_count], config.memory_label);
|
||||
strcat(info_lines[info_line_count], ": ");
|
||||
itoa((int)(mem[1] / 1024 / 1024), temp_buf);
|
||||
itoa(used / 1024, temp_buf);
|
||||
strcat(info_lines[info_line_count], temp_buf);
|
||||
strcat(info_lines[info_line_count], "MiB / ");
|
||||
itoa((int)(mem[0] / 1024 / 1024), temp_buf);
|
||||
itoa(total / 1024, temp_buf);
|
||||
strcat(info_lines[info_line_count], temp_buf);
|
||||
strcat(info_lines[info_line_count++], "MiB");
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
printf("Usage: touch <filename>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
if (sys_exists(argv[1])) {
|
||||
// Just return success if it exists (simplification)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd = sys_open(argv[1], "w");
|
||||
if (fd < 0) {
|
||||
printf("Error: Cannot create %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return 0;
|
||||
}
|
||||
236
src/userland/cli/uname.c
Normal file
236
src/userland/cli/uname.c
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static void str_copy(char *dst, const char *src, int max_len) {
|
||||
int i = 0;
|
||||
if (!dst || max_len <= 0) return;
|
||||
if (!src) { dst[0] = 0; return; }
|
||||
while (i < max_len - 1 && src[i]) {
|
||||
dst[i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static bool starts_with(const char *s, const char *prefix) {
|
||||
if (!s || !prefix) return false;
|
||||
while (*prefix) {
|
||||
if (*s != *prefix) return false;
|
||||
s++; prefix++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void trim_end(char *s) {
|
||||
int len;
|
||||
if (!s) return;
|
||||
len = (int)strlen(s);
|
||||
while (len > 0) {
|
||||
char c = s[len - 1];
|
||||
if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
|
||||
s[len - 1] = 0;
|
||||
len--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char *trim_start(const char *s) {
|
||||
if (!s) return "";
|
||||
while (*s == ' ' || *s == '\t') s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void copy_range(char *dst, const char *start, int len, int max_len) {
|
||||
int i;
|
||||
if (!dst || max_len <= 0) return;
|
||||
if (!start || len <= 0) { dst[0] = 0; return; }
|
||||
if (len > max_len - 1) len = max_len - 1;
|
||||
for (i = 0; i < len; i++) dst[i] = start[i];
|
||||
dst[len] = 0;
|
||||
trim_end(dst);
|
||||
}
|
||||
|
||||
static void copy_line(const char *start, char *out, int out_len) {
|
||||
int i = 0;
|
||||
if (!start || !out || out_len <= 0) return;
|
||||
while (start[i] && start[i] != '\n' && start[i] != '\r' && i < out_len - 1) {
|
||||
out[i] = start[i];
|
||||
i++;
|
||||
}
|
||||
out[i] = 0;
|
||||
trim_end(out);
|
||||
}
|
||||
|
||||
static int read_file_to_buf(const char *path, char *buf, int max_len) {
|
||||
int fd;
|
||||
int bytes;
|
||||
if (!buf || max_len <= 0) return -1;
|
||||
fd = sys_open(path, "r");
|
||||
if (fd < 0) return -1;
|
||||
bytes = sys_read(fd, buf, max_len - 1);
|
||||
sys_close(fd);
|
||||
if (bytes < 0) return -1;
|
||||
buf[bytes] = 0;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void parse_version_info(const char *vbuf,
|
||||
char *os_name, int os_name_len,
|
||||
char *kernel_name, int kernel_name_len,
|
||||
char *kernel_ver, int kernel_ver_len,
|
||||
char *build_str, int build_str_len) {
|
||||
char line1[256] = {0};
|
||||
char line2[256] = {0};
|
||||
char line3[256] = {0};
|
||||
const char *l2;
|
||||
const char *l3;
|
||||
const char *end;
|
||||
|
||||
if (!vbuf || !vbuf[0]) return;
|
||||
|
||||
copy_line(vbuf, line1, sizeof(line1));
|
||||
l2 = strchr(vbuf, '\n');
|
||||
if (l2) {
|
||||
l2++;
|
||||
copy_line(l2, line2, sizeof(line2));
|
||||
l3 = strchr(l2, '\n');
|
||||
if (l3) {
|
||||
l3++;
|
||||
copy_line(l3, line3, sizeof(line3));
|
||||
}
|
||||
}
|
||||
|
||||
if (line1[0]) {
|
||||
end = strstr(line1, " [");
|
||||
if (!end) end = strstr(line1, " Version");
|
||||
if (!end) end = line1 + (int)strlen(line1);
|
||||
copy_range(os_name, line1, (int)(end - line1), os_name_len);
|
||||
}
|
||||
|
||||
if (line2[0]) {
|
||||
const char *p = line2;
|
||||
if (starts_with(p, "Kernel:")) {
|
||||
p += 7;
|
||||
if (*p == ' ') p++;
|
||||
}
|
||||
p = trim_start(p);
|
||||
if (*p) {
|
||||
const char *t = p;
|
||||
while (*t && *t != ' ') t++;
|
||||
copy_range(kernel_name, p, (int)(t - p), kernel_name_len);
|
||||
while (*t == ' ') t++;
|
||||
if (*t) copy_range(kernel_ver, t, (int)strlen(t), kernel_ver_len);
|
||||
}
|
||||
}
|
||||
|
||||
if (line3[0]) {
|
||||
const char *p = line3;
|
||||
if (starts_with(p, "Build:")) {
|
||||
p += 6;
|
||||
if (*p == ' ') p++;
|
||||
}
|
||||
p = trim_start(p);
|
||||
if (*p) copy_range(build_str, p, (int)strlen(p), build_str_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_cpu_model(const char *buf, char *out, int out_len) {
|
||||
const char *p;
|
||||
const char *colon;
|
||||
const char *end;
|
||||
if (!buf || !out || out_len <= 0) return;
|
||||
p = strstr(buf, "model name");
|
||||
if (!p) return;
|
||||
colon = strchr(p, ':');
|
||||
if (!colon) return;
|
||||
colon++;
|
||||
colon = trim_start(colon);
|
||||
end = colon;
|
||||
while (*end && *end != '\n' && *end != '\r') end++;
|
||||
copy_range(out, colon, (int)(end - colon), out_len);
|
||||
}
|
||||
|
||||
static void print_usage(void) {
|
||||
printf("Usage: uname [-amnoprsv]\n");
|
||||
}
|
||||
|
||||
static void print_field(const char *value, bool *first) {
|
||||
if (!value || !value[0]) value = "Unknown";
|
||||
if (!*first) printf(" ");
|
||||
printf("%s", value);
|
||||
*first = false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool want_s = false;
|
||||
bool want_n = false;
|
||||
bool want_r = false;
|
||||
bool want_v = false;
|
||||
bool want_m = false;
|
||||
bool want_p = false;
|
||||
bool want_o = false;
|
||||
bool any_flags = false;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (!arg || arg[0] != '-' || arg[1] == 0) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
for (int j = 1; arg[j]; j++) {
|
||||
char c = arg[j];
|
||||
if (c == 'a') {
|
||||
want_s = want_n = want_r = want_v = want_m = want_p = want_o = true;
|
||||
any_flags = true;
|
||||
} else if (c == 's') { want_s = true; any_flags = true; }
|
||||
else if (c == 'n') { want_n = true; any_flags = true; }
|
||||
else if (c == 'r') { want_r = true; any_flags = true; }
|
||||
else if (c == 'v') { want_v = true; any_flags = true; }
|
||||
else if (c == 'm') { want_m = true; any_flags = true; }
|
||||
else if (c == 'p') { want_p = true; any_flags = true; }
|
||||
else if (c == 'o') { want_o = true; any_flags = true; }
|
||||
else { print_usage(); return 1; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_flags) want_s = true;
|
||||
|
||||
char vbuf[1024] = {0};
|
||||
char cpubuf[2048] = {0};
|
||||
char os_name[64] = "Unknown";
|
||||
char kernel_name[64] = "Unknown";
|
||||
char kernel_ver[64] = "Unknown";
|
||||
char build_str[64] = "Unknown";
|
||||
char processor[128] = {0};
|
||||
|
||||
if (read_file_to_buf("/proc/version", vbuf, sizeof(vbuf)) > 0) {
|
||||
parse_version_info(vbuf, os_name, sizeof(os_name), kernel_name, sizeof(kernel_name),
|
||||
kernel_ver, sizeof(kernel_ver), build_str, sizeof(build_str));
|
||||
}
|
||||
|
||||
if (read_file_to_buf("/proc/cpuinfo", cpubuf, sizeof(cpubuf)) > 0) {
|
||||
parse_cpu_model(cpubuf, processor, sizeof(processor));
|
||||
}
|
||||
|
||||
const char *nodename = "boredos";
|
||||
const char *machine = "x86_64";
|
||||
if (!processor[0]) str_copy(processor, machine, sizeof(processor));
|
||||
|
||||
bool first = true;
|
||||
if (want_s) print_field(kernel_name, &first);
|
||||
if (want_n) print_field(nodename, &first);
|
||||
if (want_r) print_field(kernel_ver, &first);
|
||||
if (want_v) print_field(build_str, &first);
|
||||
if (want_m) print_field(machine, &first);
|
||||
if (want_p) print_field(processor, &first);
|
||||
if (want_o) print_field(os_name, &first);
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
519
src/userland/games/2048.c
Normal file
519
src/userland/games/2048.c
Normal file
@@ -0,0 +1,519 @@
|
||||
#include "libc/syscall.h"
|
||||
#include "libc/libui.h"
|
||||
#include "libc/stdlib.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
@Lluciocc
|
||||
2048 for BoredOS
|
||||
Controls:
|
||||
- WASD keys or arrow keys or numpad keys to move
|
||||
- R key to restart
|
||||
*/
|
||||
|
||||
#define WINDOW_W 300
|
||||
#define WINDOW_H 430
|
||||
|
||||
#define BOARD_SIZE 4
|
||||
#define TILE_SIZE 56
|
||||
#define TILE_GAP 8
|
||||
#define BOARD_X 18
|
||||
#define BOARD_Y 96
|
||||
|
||||
#define BTN_W 54
|
||||
#define BTN_H 30
|
||||
|
||||
#define COLOR_BG 0xFF121212
|
||||
#define COLOR_PANEL 0xFF202020
|
||||
#define COLOR_PANEL_2 0xFF2A2A2A
|
||||
#define COLOR_BORDER 0xFF3D3D3D
|
||||
#define COLOR_TEXT 0xFFF2F2F2
|
||||
#define COLOR_TEXT_DARK 0xFF202020
|
||||
#define COLOR_MUTED 0xFFBBBBBB
|
||||
#define COLOR_ACCENT 0xFF6EA8FE
|
||||
#define COLOR_GREEN 0xFF69DB7C
|
||||
#define COLOR_RED 0xFFFF6B6B
|
||||
#define COLOR_EMPTY_TILE 0xFF2D2D2D
|
||||
|
||||
static int board[BOARD_SIZE][BOARD_SIZE];
|
||||
static int score = 0;
|
||||
static int best_tile = 0;
|
||||
static bool game_over = false;
|
||||
static bool game_won = false;
|
||||
static bool has_moved_last_turn = false;
|
||||
|
||||
static uint32_t random_seed = 0xC0FFEE12u;
|
||||
|
||||
static uint32_t random_next(void) {
|
||||
random_seed = random_seed * 1664525u + 1013904223u;
|
||||
return random_seed;
|
||||
}
|
||||
|
||||
static int max_int(int a, int b) {
|
||||
return (a > b) ? a : b;
|
||||
}
|
||||
|
||||
static void clear_board(void) {
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
board[y][x] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int count_empty_cells(void) {
|
||||
int count = 0;
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
if (board[y][x] == 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void add_random_tile(void) {
|
||||
int empty_count = count_empty_cells();
|
||||
if (empty_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int pick = (int)(random_next() % (uint32_t)empty_count);
|
||||
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
if (board[y][x] == 0) {
|
||||
if (pick == 0) {
|
||||
/* 90% chance of a 2, 10% chance of a 4 */
|
||||
board[y][x] = ((random_next() % 10u) == 0u) ? 4 : 2;
|
||||
best_tile = max_int(best_tile, board[y][x]);
|
||||
return;
|
||||
}
|
||||
pick--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool can_make_any_move(void) {
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
if (board[y][x] == 0) {
|
||||
return true;
|
||||
}
|
||||
if (x + 1 < BOARD_SIZE && board[y][x] == board[y][x + 1]) {
|
||||
return true;
|
||||
}
|
||||
if (y + 1 < BOARD_SIZE && board[y][x] == board[y + 1][x]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void refresh_end_state(void) {
|
||||
game_won = false;
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
if (board[y][x] >= 2048) {
|
||||
game_won = true;
|
||||
}
|
||||
if (board[y][x] > best_tile) {
|
||||
best_tile = board[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game_over = !can_make_any_move();
|
||||
}
|
||||
|
||||
static void init_game(void) {
|
||||
clear_board();
|
||||
score = 0;
|
||||
best_tile = 0;
|
||||
game_over = false;
|
||||
game_won = false;
|
||||
has_moved_last_turn = false;
|
||||
|
||||
add_random_tile();
|
||||
add_random_tile();
|
||||
refresh_end_state();
|
||||
}
|
||||
|
||||
static void copy_line_from_row(int row, int out[BOARD_SIZE]) {
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
out[i] = board[row][i];
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_line_to_row(int row, const int in[BOARD_SIZE]) {
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
board[row][i] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_line_from_col(int col, int out[BOARD_SIZE]) {
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
out[i] = board[i][col];
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_line_to_col(int col, const int in[BOARD_SIZE]) {
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
board[i][col] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void reverse_line(int line[BOARD_SIZE]) {
|
||||
for (int i = 0; i < BOARD_SIZE / 2; i++) {
|
||||
int tmp = line[i];
|
||||
line[i] = line[BOARD_SIZE - 1 - i];
|
||||
line[BOARD_SIZE - 1 - i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static bool slide_and_merge_line_left(int line[BOARD_SIZE]) {
|
||||
int compact[BOARD_SIZE];
|
||||
int merged[BOARD_SIZE];
|
||||
int compact_len = 0;
|
||||
int write_index = 0;
|
||||
bool changed = false;
|
||||
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
merged[i] = 0;
|
||||
if (line[i] != 0) {
|
||||
compact[compact_len++] = line[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < compact_len; i++) {
|
||||
if (i + 1 < compact_len && compact[i] == compact[i + 1]) {
|
||||
int value = compact[i] * 2;
|
||||
merged[write_index++] = value;
|
||||
score += value;
|
||||
best_tile = max_int(best_tile, value);
|
||||
i++; /* Skip the second tile, it has been merged. */
|
||||
} else {
|
||||
merged[write_index++] = compact[i];
|
||||
}
|
||||
}
|
||||
|
||||
while (write_index < BOARD_SIZE) {
|
||||
merged[write_index++] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
if (line[i] != merged[i]) {
|
||||
changed = true;
|
||||
}
|
||||
line[i] = merged[i];
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
MOVE_LEFT,
|
||||
MOVE_RIGHT,
|
||||
MOVE_UP,
|
||||
MOVE_DOWN
|
||||
} move_dir_t;
|
||||
|
||||
static bool apply_move(move_dir_t dir) {
|
||||
bool changed = false;
|
||||
|
||||
if (game_over) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
|
||||
for (int row = 0; row < BOARD_SIZE; row++) {
|
||||
int line[BOARD_SIZE];
|
||||
copy_line_from_row(row, line);
|
||||
|
||||
if (dir == MOVE_RIGHT) {
|
||||
reverse_line(line);
|
||||
}
|
||||
|
||||
if (slide_and_merge_line_left(line)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (dir == MOVE_RIGHT) {
|
||||
reverse_line(line);
|
||||
}
|
||||
|
||||
copy_line_to_row(row, line);
|
||||
}
|
||||
} else {
|
||||
for (int col = 0; col < BOARD_SIZE; col++) {
|
||||
int line[BOARD_SIZE];
|
||||
copy_line_from_col(col, line);
|
||||
|
||||
if (dir == MOVE_DOWN) {
|
||||
reverse_line(line);
|
||||
}
|
||||
|
||||
if (slide_and_merge_line_left(line)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (dir == MOVE_DOWN) {
|
||||
reverse_line(line);
|
||||
}
|
||||
|
||||
copy_line_to_col(col, line);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
add_random_tile();
|
||||
}
|
||||
|
||||
has_moved_last_turn = changed;
|
||||
refresh_end_state();
|
||||
return changed;
|
||||
}
|
||||
|
||||
static uint32_t get_tile_color(int value) {
|
||||
switch (value) {
|
||||
case 0: return COLOR_EMPTY_TILE;
|
||||
case 2: return 0xFFEEE4DA;
|
||||
case 4: return 0xFFEDE0C8;
|
||||
case 8: return 0xFFF2B179;
|
||||
case 16: return 0xFFF59563;
|
||||
case 32: return 0xFFF67C5F;
|
||||
case 64: return 0xFFF65E3B;
|
||||
case 128: return 0xFFEDCF72;
|
||||
case 256: return 0xFFEDCC61;
|
||||
case 512: return 0xFFEDC850;
|
||||
case 1024: return 0xFFEDC53F;
|
||||
case 2048: return 0xFFEDC22E;
|
||||
default: return 0xFF3C91E6;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t get_tile_text_color(int value) {
|
||||
(void)value;
|
||||
return 0xFF000000; // for visibility
|
||||
}
|
||||
|
||||
static void int_to_text(int value, char *out) {
|
||||
itoa(value, out);
|
||||
}
|
||||
|
||||
static void draw_centered_text(ui_window_t win, int x, int y, int w, int h,
|
||||
const char *text, uint32_t color, float scale) {
|
||||
(void)scale;
|
||||
|
||||
uint32_t text_w = ui_get_string_width(text);
|
||||
uint32_t text_h = ui_get_font_height();
|
||||
|
||||
int draw_x = x + (w - (int)text_w) / 2;
|
||||
int draw_y = y + (h - (int)text_h) / 2;
|
||||
|
||||
ui_draw_string(win, draw_x, draw_y, text, color);
|
||||
}
|
||||
|
||||
static void draw_button(ui_window_t win, int x, int y, int w, int h,
|
||||
const char *label, uint32_t color) {
|
||||
ui_draw_rounded_rect_filled(win, x, y, w, h, 6, color);
|
||||
draw_centered_text(win, x, y, w, h, label, COLOR_TEXT, 1.0f);
|
||||
}
|
||||
|
||||
static void draw_score_box(ui_window_t win, int x, int y, int w, int h,
|
||||
const char *title, int value) {
|
||||
char buf[16];
|
||||
int_to_text(value, buf);
|
||||
|
||||
ui_draw_rounded_rect_filled(win, x, y, w, h, 8, COLOR_PANEL_2);
|
||||
draw_centered_text(win, x, y + 3, w, 14, title, COLOR_MUTED, 0.8f);
|
||||
draw_centered_text(win, x, y + 16, w, 18, buf, COLOR_TEXT, 1.0f);
|
||||
}
|
||||
|
||||
static void draw_tile(ui_window_t win, int x, int y, int value) {
|
||||
char buf[16];
|
||||
float scale = 1.6f;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, x, y, TILE_SIZE, TILE_SIZE, 8, get_tile_color(value));
|
||||
|
||||
if (value == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int_to_text(value, buf);
|
||||
|
||||
if (value >= 1000) {
|
||||
scale = 1.2f;
|
||||
} else if (value >= 100) {
|
||||
scale = 1.4f;
|
||||
}
|
||||
|
||||
draw_centered_text(
|
||||
win,
|
||||
x, y,
|
||||
TILE_SIZE, TILE_SIZE,
|
||||
buf,
|
||||
0xFF000000,
|
||||
scale
|
||||
);
|
||||
}
|
||||
|
||||
static void draw_board(ui_window_t win) {
|
||||
int board_w = BOARD_SIZE * TILE_SIZE + (BOARD_SIZE + 1) * TILE_GAP;
|
||||
int board_h = board_w;
|
||||
|
||||
ui_draw_rounded_rect_filled(win, BOARD_X, BOARD_Y, board_w, board_h, 10, COLOR_PANEL_2);
|
||||
|
||||
for (int y = 0; y < BOARD_SIZE; y++) {
|
||||
for (int x = 0; x < BOARD_SIZE; x++) {
|
||||
int px = BOARD_X + TILE_GAP + x * (TILE_SIZE + TILE_GAP);
|
||||
int py = BOARD_Y + TILE_GAP + y * (TILE_SIZE + TILE_GAP);
|
||||
draw_tile(win, px, py, board[y][x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_status(ui_window_t win) {
|
||||
if (game_over) {
|
||||
ui_draw_string(win, 18, 70, "Game over", COLOR_RED);
|
||||
} else if (game_won) {
|
||||
ui_draw_string(win, 18, 70, "2048 reached - keep going", COLOR_GREEN);
|
||||
} else {
|
||||
ui_draw_string(win, 18, 70, "Combine tiles to reach 2048", COLOR_MUTED);
|
||||
}
|
||||
}
|
||||
|
||||
static void game_paint(ui_window_t win) {
|
||||
ui_draw_rect(win, 0, 0, WINDOW_W, WINDOW_H, COLOR_BG);
|
||||
|
||||
ui_draw_string_scaled(win, 18, 12, "2048", COLOR_TEXT, 1.6f);
|
||||
ui_draw_string(win, 18, 42, "Use WASD keys", COLOR_MUTED);
|
||||
|
||||
draw_score_box(win, 155, 14, 56, 40, "SCORE", score);
|
||||
draw_score_box(win, 220, 14, 62, 40, "BEST", best_tile);
|
||||
draw_status(win);
|
||||
|
||||
draw_board(win);
|
||||
/* Not recommended to use
|
||||
draw_button(win, 18, 352, 86, 30, "Restart", COLOR_ACCENT);
|
||||
draw_button(win, 123, 352, BTN_W, BTN_H, "Left", COLOR_BORDER);
|
||||
draw_button(win, 186, 352, BTN_W, BTN_H, "Right", COLOR_BORDER);
|
||||
draw_button(win, 92, 390, BTN_W, BTN_H, "Up", COLOR_BORDER);
|
||||
draw_button(win, 155, 390, BTN_W, BTN_H, "Down", COLOR_BORDER);
|
||||
*/
|
||||
}
|
||||
|
||||
static bool point_in_rect(int px, int py, int x, int y, int w, int h) {
|
||||
return px >= x && px < x + w && py >= y && py < y + h;
|
||||
}
|
||||
|
||||
/*
|
||||
37 = left arrow
|
||||
38 = up arrow
|
||||
39 = right arrow
|
||||
40 = down arrow
|
||||
72 = numpad 8
|
||||
75 = numpad 4
|
||||
77 = numpad 6
|
||||
80 = numpad 2
|
||||
key = letter
|
||||
*/
|
||||
static bool is_left_key(int key) {
|
||||
return key == 'a' || key == 'A' || key == 37 || key == 75;
|
||||
}
|
||||
|
||||
static bool is_right_key(int key) {
|
||||
return key == 'd' || key == 'D' || key == 39 || key == 77;
|
||||
}
|
||||
|
||||
static bool is_up_key(int key) {
|
||||
return key == 'w' || key == 'W' || key == 38 || key == 72;
|
||||
}
|
||||
|
||||
static bool is_down_key(int key) {
|
||||
return key == 's' || key == 'S' || key == 40 || key == 80;
|
||||
}
|
||||
|
||||
static void handle_click(int x, int y) {
|
||||
if (point_in_rect(x, y, 18, 352, 86, 30)) {
|
||||
init_game();
|
||||
return;
|
||||
}
|
||||
if (point_in_rect(x, y, 123, 352, BTN_W, BTN_H)) {
|
||||
apply_move(MOVE_LEFT);
|
||||
return;
|
||||
}
|
||||
if (point_in_rect(x, y, 186, 352, BTN_W, BTN_H)) {
|
||||
apply_move(MOVE_RIGHT);
|
||||
return;
|
||||
}
|
||||
if (point_in_rect(x, y, 92, 390, BTN_W, BTN_H)) {
|
||||
apply_move(MOVE_UP);
|
||||
return;
|
||||
}
|
||||
if (point_in_rect(x, y, 155, 390, BTN_W, BTN_H)) {
|
||||
apply_move(MOVE_DOWN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_key(int key) {
|
||||
if (key == 'r' || key == 'R') {
|
||||
init_game();
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_left_key(key)) {
|
||||
apply_move(MOVE_LEFT);
|
||||
} else if (is_right_key(key)) {
|
||||
apply_move(MOVE_RIGHT);
|
||||
} else if (is_up_key(key)) {
|
||||
apply_move(MOVE_UP);
|
||||
} else if (is_down_key(key)) {
|
||||
apply_move(MOVE_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
ui_window_t win = ui_window_create("2048", 240, 120, WINDOW_W, WINDOW_H);
|
||||
if (!win) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
init_game();
|
||||
|
||||
gui_event_t ev;
|
||||
while (1) {
|
||||
if (ui_get_event(win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_PAINT) {
|
||||
game_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H);
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
handle_click(ev.arg1, ev.arg2);
|
||||
game_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H);
|
||||
} else if (ev.type == GUI_EVENT_KEY) {
|
||||
handle_key(ev.arg1);
|
||||
game_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H);
|
||||
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||
sys_exit(0);
|
||||
} else if (ev.type == GUI_EVENT_RESIZE) {
|
||||
game_paint(win);
|
||||
ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H);
|
||||
}
|
||||
} else {
|
||||
sys_yield();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -149,26 +149,12 @@ char *strrchr(const char *s, int c) {
|
||||
if (c == 0) last = s;
|
||||
return (char*)last;
|
||||
}
|
||||
char *strchr(const char *s, int c) {
|
||||
while (*s) { if (*s == c) return (char*)s; s++; }
|
||||
if (c == 0) return (char*)s;
|
||||
return NULL;
|
||||
}
|
||||
char *strdup(const char *s) {
|
||||
size_t len = strlen(s) + 1;
|
||||
char *dup = malloc(len);
|
||||
if (dup) memcpy(dup, s, len);
|
||||
return dup;
|
||||
}
|
||||
char *strstr(const char *haystack, const char *needle) {
|
||||
size_t n = strlen(needle);
|
||||
if (!n) return (char *)haystack;
|
||||
while (*haystack) {
|
||||
if (!strncmp(haystack, needle, n)) return (char *)haystack;
|
||||
haystack++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int toupper(int c) { return (c >= 'a' && c <= 'z') ? c - 32 : c; }
|
||||
int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
|
||||
|
||||
@@ -39,8 +39,19 @@ void DG_SleepMs(uint32_t ms) {
|
||||
}
|
||||
|
||||
uint32_t DG_GetTicksMs(void) {
|
||||
uint64_t ticks = sys_system(16, 0, 0, 0, 0); // SYSTEM_CMD_UPTIME = 16 (100Hz)
|
||||
return (uint32_t)(ticks * 10);
|
||||
int fd = sys_open("/proc/uptime", "r");
|
||||
if (fd < 0) return 0;
|
||||
char buf[128];
|
||||
int bytes = sys_read(fd, buf, 127);
|
||||
sys_close(fd);
|
||||
if (bytes <= 0) return 0;
|
||||
buf[bytes] = 0;
|
||||
|
||||
char *p = strstr(buf, "Raw_Ticks:");
|
||||
if (!p) return 0;
|
||||
uint32_t ticks = atoi(p + 10);
|
||||
// 60Hz to ms: ticks * 1000 / 60 = ticks * 50 / 3
|
||||
return (ticks * 50) / 3;
|
||||
}
|
||||
|
||||
void DG_SetWindowTitle(const char * title) {
|
||||
@@ -108,7 +119,7 @@ int DG_GetKey(int* pressed, unsigned char* key) {
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
char* fake_argv[] = {"doom", "-iwad", "A:/Library/DOOM/doom1.wad"};
|
||||
char* fake_argv[] = {"doom", "-iwad", "/Library/DOOM/doom1.wad"};
|
||||
doomgeneric_Create(3, fake_argv);
|
||||
|
||||
while (1) {
|
||||
|
||||
@@ -95,36 +95,39 @@ static void about_paint(ui_window_t win) {
|
||||
draw_ascii_logo(win, 14, offset_y);
|
||||
|
||||
int fh = ui_get_font_height();
|
||||
os_info_t os_info;
|
||||
sys_get_os_info(&os_info);
|
||||
int fd_v = sys_open("/proc/version", "r");
|
||||
char v_buf[1024]; v_buf[0] = 0;
|
||||
if (fd_v >= 0) {
|
||||
int b = sys_read(fd_v, v_buf, 1023);
|
||||
v_buf[b] = 0;
|
||||
sys_close(fd_v);
|
||||
}
|
||||
|
||||
char os_name_str[128];
|
||||
os_name_str[0] = 0;
|
||||
strcat(os_name_str, os_info.os_name);
|
||||
strcat(os_name_str, " '");
|
||||
strcat(os_name_str, os_info.os_codename);
|
||||
strcat(os_name_str, "'");
|
||||
char os_name_str[128] = "Unknown OS";
|
||||
char os_version_str[128] = "Unknown Version";
|
||||
char kernel_version_str[128] = "Unknown Kernel";
|
||||
char build_date_str[128] = "Unknown Build";
|
||||
|
||||
char os_version_str[128];
|
||||
os_version_str[0] = 0;
|
||||
strcat(os_version_str, os_info.os_name);
|
||||
strcat(os_version_str, " Version ");
|
||||
strcat(os_version_str, os_info.os_version);
|
||||
if (v_buf[0]) {
|
||||
char *line1 = v_buf;
|
||||
char *line2 = strchr(line1, '\n'); if (line2) { *line2 = 0; line2++; }
|
||||
char *line3 = line2 ? strchr(line2, '\n') : NULL; if (line3) { *line3 = 0; line3++; }
|
||||
|
||||
char kernel_version_str[128];
|
||||
kernel_version_str[0] = 0;
|
||||
strcat(kernel_version_str, os_info.kernel_name);
|
||||
strcat(kernel_version_str, " Version ");
|
||||
strcat(kernel_version_str, os_info.kernel_version);
|
||||
strcat(kernel_version_str, " ");
|
||||
strcat(kernel_version_str, os_info.build_arch);
|
||||
|
||||
char build_date_str[128];
|
||||
build_date_str[0] = 0;
|
||||
strcat(build_date_str, "Build Date: ");
|
||||
strcat(build_date_str, os_info.build_date);
|
||||
strcat(build_date_str, " ");
|
||||
strcat(build_date_str, os_info.build_time);
|
||||
strcpy(os_name_str, line1);
|
||||
if (line2) {
|
||||
strcpy(os_version_str, line2);
|
||||
}
|
||||
if (line3) {
|
||||
strcpy(kernel_version_str, line3);
|
||||
char *line4 = strchr(line3, '\n');
|
||||
if (line4) {
|
||||
*line4 = 0; line4++;
|
||||
strcpy(build_date_str, line4);
|
||||
char *line5 = strchr(build_date_str, '\n');
|
||||
if (line5) *line5 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui_draw_string(win, offset_x, offset_y + 105, os_name_str, 0xFFFFFFFF);
|
||||
ui_draw_string(win, offset_x, offset_y + 105 + fh, os_version_str, 0xFFFFFFFF);
|
||||
|
||||
@@ -32,19 +32,6 @@ static int win_h = 960;
|
||||
static char history_stack[HISTORY_MAX][512];
|
||||
static int history_count = 0;
|
||||
|
||||
static char* strstr(const char* haystack, const char* needle) {
|
||||
if (!*needle) return (char*)haystack;
|
||||
for (; *haystack; haystack++) {
|
||||
const char *h = haystack;
|
||||
const char *n = needle;
|
||||
while (*h && *n && *h == *n) {
|
||||
h++; n++;
|
||||
}
|
||||
if (!*n) return (char*)haystack;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* str_istrstr(const char* haystack, const char* needle) {
|
||||
if (!*needle) return (char*)haystack;
|
||||
for (; *haystack; haystack++) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ static void notepad_ensure_cursor_visible(int h) {
|
||||
}
|
||||
|
||||
static void notepad_load_state() {
|
||||
int fd = sys_open("A:/tmp/notepad_state.txt", "r");
|
||||
int fd = sys_open("/tmp/notepad_state.txt", "r");
|
||||
if (fd >= 0) {
|
||||
sys_serial_write("Notepad: Loading state...\n");
|
||||
buf_len = sys_read(fd, buffer, NOTEPAD_BUF_SIZE - 1);
|
||||
@@ -50,8 +50,8 @@ static void notepad_load_state() {
|
||||
|
||||
static void notepad_save_state() {
|
||||
// Ensure dir exists
|
||||
sys_mkdir("A:/tmp");
|
||||
int fd = sys_open("A:/tmp/notepad_state.txt", "w");
|
||||
sys_mkdir("/tmp");
|
||||
int fd = sys_open("/tmp/notepad_state.txt", "w");
|
||||
if (fd >= 0) {
|
||||
sys_write_fs(fd, buffer, buf_len);
|
||||
sys_close(fd);
|
||||
|
||||
@@ -28,7 +28,7 @@ static uint32_t *canvas_buffer = NULL;
|
||||
static uint32_t current_color = COLOR_BLACK;
|
||||
static int last_mx = -1;
|
||||
static int last_my = -1;
|
||||
static char current_file_path[256] = "/Desktop/drawing.pnt";
|
||||
static char current_file_path[256] = "/root/Desktop/drawing.pnt";
|
||||
|
||||
static void paint_strcpy(char *dest, const char *src) {
|
||||
while (*src) *dest++ = *src++;
|
||||
@@ -79,12 +79,7 @@ static void paint_paint(ui_window_t win) {
|
||||
|
||||
// Draw canvas content
|
||||
if (canvas_buffer) {
|
||||
for (int y = 0; y < CANVAS_H; y++) {
|
||||
for (int x = 0; x < CANVAS_W; x++) {
|
||||
uint32_t color = canvas_buffer[y * CANVAS_W + x];
|
||||
ui_draw_rect(win, canvas_x + x, canvas_y + y, 1, 1, color);
|
||||
}
|
||||
}
|
||||
ui_draw_image(win, canvas_x, canvas_y, CANVAS_W, CANVAS_H, canvas_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +91,6 @@ static void paint_put_brush(ui_window_t win, int cx, int cy, int *min_x, int *mi
|
||||
int py = cy + dy;
|
||||
if (px >= 0 && px < CANVAS_W && py >= 0 && py < CANVAS_H) {
|
||||
canvas_buffer[py * CANVAS_W + px] = current_color;
|
||||
ui_draw_rect(win, 60 + px, 0 + py, 1, 1, current_color);
|
||||
|
||||
if (px < *min_x) *min_x = px;
|
||||
if (py < *min_y) *min_y = py;
|
||||
@@ -140,6 +134,7 @@ void paint_handle_mouse(ui_window_t win, int x, int y) {
|
||||
}
|
||||
|
||||
if (min_x <= max_x && min_y <= max_y) {
|
||||
ui_draw_image(win, 60, 0, CANVAS_W, CANVAS_H, canvas_buffer);
|
||||
ui_mark_dirty(win, 60 + min_x, 0 + min_y, (max_x - min_x) + 1, (max_y - min_y) + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ int main(int argc, char **argv) {
|
||||
uint64_t dt[6] = {0};
|
||||
syscall2(SYS_GUI, GUI_CMD_GET_DATETIME, (uint64_t)dt);
|
||||
|
||||
strcpy(g_filename, "/Desktop/screenshot-");
|
||||
strcpy(g_filename, "/root/Desktop/screenshot-");
|
||||
append_num((int)dt[0], 4); // Year
|
||||
append_num((int)dt[1], 2); // Month
|
||||
append_num((int)dt[2], 2); // Day
|
||||
@@ -110,7 +110,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
// Show notification
|
||||
char notif[256] = "Saved ";
|
||||
strcat(notif, g_filename + 9); // Skip "/Desktop/"
|
||||
strcat(notif, g_filename + 14); // Skip "/root/Desktop/"
|
||||
syscall2(SYS_GUI, GUI_CMD_SHOW_NOTIFICATION, (uint64_t)notif);
|
||||
} else {
|
||||
res = 0;
|
||||
|
||||
@@ -32,38 +32,111 @@ static uint64_t kernel_ticks_prev = 0;
|
||||
static uint64_t total_mem_system = 0;
|
||||
static uint64_t used_mem_system = 0;
|
||||
static char cpu_model_name[64] = "Unknown CPU";
|
||||
static int cpu_cores = 1;
|
||||
|
||||
typedef struct {
|
||||
size_t total_memory;
|
||||
size_t used_memory;
|
||||
size_t available_memory;
|
||||
size_t allocated_blocks;
|
||||
size_t free_blocks;
|
||||
size_t largest_free_block;
|
||||
size_t smallest_free_block;
|
||||
size_t fragmentation_percent;
|
||||
size_t peak_memory_used;
|
||||
} MemStats;
|
||||
static int find_value(const char *buf, const char *key) {
|
||||
char *p = (char*)buf;
|
||||
int key_len = strlen(key);
|
||||
while (*p) {
|
||||
if (memcmp(p, key, key_len) == 0 && p[key_len] == ':') {
|
||||
p += key_len + 1;
|
||||
while (*p == ' ') p++;
|
||||
return atoi(p);
|
||||
}
|
||||
while (*p && *p != '\n') p++;
|
||||
if (*p == '\n') p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void find_string(const char *buf, const char *key, char *out, int max_len) {
|
||||
char *p = (char*)buf;
|
||||
int key_len = strlen(key);
|
||||
while (*p) {
|
||||
if (memcmp(p, key, key_len) == 0 && p[key_len] == ':') {
|
||||
p += key_len + 1;
|
||||
while (*p == ' ') p++;
|
||||
int i = 0;
|
||||
while (*p && *p != '\n' && i < max_len - 1) {
|
||||
out[i++] = *p++;
|
||||
}
|
||||
out[i] = 0;
|
||||
return;
|
||||
}
|
||||
while (*p && *p != '\n') p++;
|
||||
if (*p == '\n') p++;
|
||||
}
|
||||
strcpy(out, "Unknown");
|
||||
}
|
||||
|
||||
static void update_proc_list(void) {
|
||||
proc_count = sys_system(SYSTEM_CMD_PROCESS_LIST, (uint64_t)proc_list, 32, 0, 0);
|
||||
FAT32_FileInfo entries[64];
|
||||
int count = sys_list("/proc", entries, 64);
|
||||
if (count < 0) return;
|
||||
|
||||
uint64_t uptime_now = sys_system(SYSTEM_CMD_UPTIME, 0, 0, 0, 0);
|
||||
proc_count = 0;
|
||||
uint64_t user_ticks_now = 0;
|
||||
|
||||
for (int i = 0; i < proc_count; i++) {
|
||||
if (proc_list[i].pid != 0) {
|
||||
user_ticks_now += proc_list[i].ticks;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (entries[i].is_directory) {
|
||||
// Check if name is numeric (PID)
|
||||
bool numeric = true;
|
||||
for (int j = 0; entries[i].name[j]; j++) {
|
||||
if (entries[i].name[j] < '0' || entries[i].name[j] > '9') {
|
||||
numeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!numeric) continue;
|
||||
|
||||
int pid = atoi(entries[i].name);
|
||||
char path[64];
|
||||
strcpy(path, "/proc/");
|
||||
strcat(path, entries[i].name);
|
||||
strcat(path, "/status");
|
||||
|
||||
int fd = sys_open(path, "r");
|
||||
if (fd >= 0) {
|
||||
char buf[512];
|
||||
int bytes = sys_read(fd, buf, 511);
|
||||
sys_close(fd);
|
||||
if (bytes > 0) {
|
||||
buf[bytes] = 0;
|
||||
proc_list[proc_count].pid = pid;
|
||||
find_string(buf, "Name", proc_list[proc_count].name, 64);
|
||||
proc_list[proc_count].used_memory = (size_t)find_value(buf, "Memory") * 1024;
|
||||
uint64_t ticks = (uint64_t)find_value(buf, "Ticks");
|
||||
proc_list[proc_count].ticks = ticks;
|
||||
|
||||
proc_list[proc_count].is_idle = find_value(buf, "Idle") == 1;
|
||||
|
||||
if (!proc_list[proc_count].is_idle) user_ticks_now += ticks;
|
||||
proc_count++;
|
||||
if (proc_count >= 32) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global stats
|
||||
int fd_u = sys_open("/proc/uptime", "r");
|
||||
uint64_t uptime_now = 0;
|
||||
if (fd_u >= 0) {
|
||||
char buf[256];
|
||||
int bytes = sys_read(fd_u, buf, 255);
|
||||
sys_close(fd_u);
|
||||
if (bytes > 0) {
|
||||
buf[bytes] = 0;
|
||||
uptime_now = (uint64_t)find_value(buf, "Raw_Ticks");
|
||||
}
|
||||
}
|
||||
|
||||
if (uptime_prev > 0) {
|
||||
uint64_t total_delta = uptime_now - uptime_prev;
|
||||
if (total_delta > 0) {
|
||||
uint64_t used_delta = user_ticks_now - kernel_ticks_prev; // Reusing the global state variable for prev user_ticks
|
||||
|
||||
// On a 4 CPU system, theoretically used_delta can be 4x total_delta
|
||||
int usage = (int)((used_delta * 100) / (total_delta * 4));
|
||||
uint64_t used_delta = user_ticks_now - kernel_ticks_prev;
|
||||
int cores = cpu_cores > 0 ? cpu_cores : 1;
|
||||
int usage = (int)((used_delta * 100) / (total_delta * cores));
|
||||
if (usage > 100) usage = 100;
|
||||
cpu_history[history_idx] = usage;
|
||||
}
|
||||
@@ -72,11 +145,18 @@ static void update_proc_list(void) {
|
||||
uptime_prev = uptime_now;
|
||||
kernel_ticks_prev = user_ticks_now;
|
||||
|
||||
MemStats stats;
|
||||
sys_system(SYSTEM_CMD_MEMINFO, (uint64_t)&stats, 0, 0, 0);
|
||||
total_mem_system = stats.total_memory;
|
||||
used_mem_system = stats.used_memory;
|
||||
mem_history[history_idx] = (int)(stats.used_memory / 1024);
|
||||
int fd_m = sys_open("/proc/meminfo", "r");
|
||||
if (fd_m >= 0) {
|
||||
char buf[1024];
|
||||
int bytes = sys_read(fd_m, buf, 1023);
|
||||
sys_close(fd_m);
|
||||
if (bytes > 0) {
|
||||
buf[bytes] = 0;
|
||||
total_mem_system = (uint64_t)find_value(buf, "MemTotal") * 1024;
|
||||
used_mem_system = (uint64_t)find_value(buf, "MemUsed") * 1024;
|
||||
mem_history[history_idx] = (int)(used_mem_system / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
history_idx = (history_idx + 1) % GRAPH_POINTS;
|
||||
}
|
||||
@@ -168,9 +248,12 @@ static void draw_taskman(void) {
|
||||
// Memory Graph Area
|
||||
ui_draw_string(win_taskman, 205, 10, "MEMORY", COLOR_MEM);
|
||||
char mem_pct_label[16];
|
||||
int current_mem_pct = 0;
|
||||
if (total_mem_system > 0) current_mem_pct = (int)((used_mem_system * 100) / total_mem_system);
|
||||
itoa(current_mem_pct, mem_pct_label);
|
||||
int current_mem_pct_x10 = 0;
|
||||
if (total_mem_system > 0) current_mem_pct_x10 = (int)((used_mem_system * 1000) / total_mem_system);
|
||||
itoa(current_mem_pct_x10 / 10, mem_pct_label);
|
||||
strcat(mem_pct_label, ".");
|
||||
char frac[4]; itoa(current_mem_pct_x10 % 10, frac);
|
||||
strcat(mem_pct_label, frac);
|
||||
strcat(mem_pct_label, "%");
|
||||
ui_draw_string(win_taskman, 340, 10, mem_pct_label, COLOR_MEM);
|
||||
|
||||
@@ -255,8 +338,18 @@ static void draw_taskman(void) {
|
||||
int main(void) {
|
||||
win_taskman = ui_window_create("Task Manager", 100, 100, 400, 480);
|
||||
|
||||
// Fetch CPU model
|
||||
sys_system(SYSTEM_CMD_GET_CPU_MODEL, (uint64_t)cpu_model_name, 0, 0, 0);
|
||||
int fd_c = sys_open("/proc/cpuinfo", "r");
|
||||
if (fd_c >= 0) {
|
||||
char buf[1024];
|
||||
int bytes = sys_read(fd_c, buf, 1023);
|
||||
sys_close(fd_c);
|
||||
if (bytes > 0) {
|
||||
buf[bytes] = 0;
|
||||
find_string(buf, "Processor", cpu_model_name, 64);
|
||||
int cores = find_value(buf, "Cores");
|
||||
if (cores > 0) cpu_cores = cores;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0; i<GRAPH_POINTS; i++) { cpu_history[i] = 0; mem_history[i] = 0; }
|
||||
|
||||
|
||||
893
src/userland/gui/terminal.c
Normal file
893
src/userland/gui/terminal.c
Normal file
@@ -0,0 +1,893 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 <stdlib.h>
|
||||
#include <syscall.h>
|
||||
#include "libc/libui.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define DEFAULT_COLS 116
|
||||
#define DEFAULT_ROWS 41
|
||||
#define SCROLLBACK_LINES 800
|
||||
#define SCROLLBACK_COLS 256
|
||||
#define CHAR_W 8
|
||||
#define DEFAULT_LINE_H 10
|
||||
#define TAB_BAR_H 20
|
||||
#define CONTENT_PAD_BOTTOM 20
|
||||
#define TAB_CLOSE_W 12
|
||||
#define TAB_CLOSE_PAD 6
|
||||
#define MAX_TABS 4
|
||||
#define TTY_READ_CHUNK 512
|
||||
|
||||
typedef struct {
|
||||
char c;
|
||||
uint32_t color;
|
||||
} CharCell;
|
||||
|
||||
typedef struct {
|
||||
int tty_id;
|
||||
int bsh_pid;
|
||||
CharCell *cells;
|
||||
CharCell *scrollback;
|
||||
int *scroll_cols;
|
||||
int scroll_head;
|
||||
int scroll_count;
|
||||
int scroll_offset;
|
||||
int cursor_row;
|
||||
int cursor_col;
|
||||
uint32_t fg_color;
|
||||
uint32_t bg_color;
|
||||
|
||||
int ansi_state;
|
||||
int ansi_params[8];
|
||||
int ansi_param_count;
|
||||
int saved_row;
|
||||
int saved_col;
|
||||
} TerminalSession;
|
||||
|
||||
static ui_window_t g_win;
|
||||
static TerminalSession g_tabs[MAX_TABS];
|
||||
static int g_tab_count = 0;
|
||||
static int g_active_tab = 0;
|
||||
static int g_cols = DEFAULT_COLS;
|
||||
static int g_rows = DEFAULT_ROWS;
|
||||
static int g_line_h = DEFAULT_LINE_H;
|
||||
static int g_win_w = DEFAULT_COLS * CHAR_W;
|
||||
static int g_win_h = TAB_BAR_H + (DEFAULT_ROWS * DEFAULT_LINE_H);
|
||||
|
||||
static void str_copy(char *dst, const char *src, int max_len) {
|
||||
int i = 0;
|
||||
if (max_len <= 0) return;
|
||||
while (i < max_len - 1 && src && src[i]) {
|
||||
dst[i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static void str_append(char *dst, const char *src, int max_len) {
|
||||
if (!dst || !src || max_len <= 0) return;
|
||||
int dlen = (int)strlen(dst);
|
||||
int i = 0;
|
||||
while (dlen + i < max_len - 1 && src[i]) {
|
||||
dst[dlen + i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[dlen + i] = 0;
|
||||
}
|
||||
|
||||
static void trim_end(char *s) {
|
||||
if (!s) return;
|
||||
int len = (int)strlen(s);
|
||||
while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r' || s[len - 1] == ' ' || s[len - 1] == '\t')) {
|
||||
s[len - 1] = 0;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
static void scrollback_init(TerminalSession *s) {
|
||||
s->scroll_head = 0;
|
||||
s->scroll_count = 0;
|
||||
s->scroll_offset = 0;
|
||||
if (s->scrollback) {
|
||||
for (int i = 0; i < SCROLLBACK_LINES * SCROLLBACK_COLS; i++) {
|
||||
s->scrollback[i].c = ' ';
|
||||
s->scrollback[i].color = s->fg_color;
|
||||
}
|
||||
}
|
||||
if (s->scroll_cols) {
|
||||
for (int i = 0; i < SCROLLBACK_LINES; i++) s->scroll_cols[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void scrollback_push_row(TerminalSession *s, const CharCell *row, int cols) {
|
||||
if (!s->scrollback || !s->scroll_cols) return;
|
||||
int idx = s->scroll_head;
|
||||
CharCell *dest = s->scrollback + (idx * SCROLLBACK_COLS);
|
||||
int copy_cols = cols;
|
||||
if (copy_cols > SCROLLBACK_COLS) copy_cols = SCROLLBACK_COLS;
|
||||
|
||||
for (int i = 0; i < SCROLLBACK_COLS; i++) {
|
||||
dest[i].c = ' ';
|
||||
dest[i].color = s->fg_color;
|
||||
}
|
||||
for (int i = 0; i < copy_cols; i++) {
|
||||
dest[i] = row[i];
|
||||
}
|
||||
|
||||
s->scroll_cols[idx] = copy_cols;
|
||||
s->scroll_head = (idx + 1) % SCROLLBACK_LINES;
|
||||
if (s->scroll_count < SCROLLBACK_LINES) {
|
||||
s->scroll_count++;
|
||||
} else if (s->scroll_offset > 0) {
|
||||
s->scroll_offset--;
|
||||
}
|
||||
|
||||
if (s->scroll_offset > 0) {
|
||||
s->scroll_offset++;
|
||||
}
|
||||
}
|
||||
|
||||
static int scrollback_max_offset(TerminalSession *s) {
|
||||
int total = s->scroll_count + g_rows;
|
||||
if (total <= g_rows) return 0;
|
||||
return total - g_rows;
|
||||
}
|
||||
|
||||
static void session_adjust_scroll(TerminalSession *s, int delta) {
|
||||
if (!s) return;
|
||||
int max_offset = scrollback_max_offset(s);
|
||||
s->scroll_offset += delta;
|
||||
if (s->scroll_offset < 0) s->scroll_offset = 0;
|
||||
if (s->scroll_offset > max_offset) s->scroll_offset = max_offset;
|
||||
}
|
||||
|
||||
static CharCell *scrollback_get_line(TerminalSession *s, int line_index, int *out_cols) {
|
||||
if (!s || line_index < 0 || line_index >= s->scroll_count) return NULL;
|
||||
int start = s->scroll_head - s->scroll_count;
|
||||
if (start < 0) start += SCROLLBACK_LINES;
|
||||
int idx = (start + line_index) % SCROLLBACK_LINES;
|
||||
if (out_cols) *out_cols = s->scroll_cols[idx];
|
||||
return s->scrollback + (idx * SCROLLBACK_COLS);
|
||||
}
|
||||
|
||||
static uint32_t ansi_color_16(int idx) {
|
||||
static uint32_t base[16] = {
|
||||
0xFF000000, 0xFFAA0000, 0xFF00AA00, 0xFFAA5500,
|
||||
0xFF0000AA, 0xFFAA00AA, 0xFF00AAAA, 0xFFAAAAAA,
|
||||
0xFF555555, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55,
|
||||
0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF
|
||||
};
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 15) idx = 15;
|
||||
return base[idx];
|
||||
}
|
||||
|
||||
static void session_reset_colors(TerminalSession *s) {
|
||||
s->fg_color = 0xFFFFFFFF;
|
||||
s->bg_color = 0xFF1E1E1E;
|
||||
}
|
||||
|
||||
static void session_clear(TerminalSession *s) {
|
||||
for (int i = 0; i < g_cols * g_rows; i++) {
|
||||
s->cells[i].c = ' ';
|
||||
s->cells[i].color = s->fg_color;
|
||||
}
|
||||
s->cursor_row = 0;
|
||||
s->cursor_col = 0;
|
||||
s->scroll_offset = 0;
|
||||
}
|
||||
|
||||
static void session_scroll(TerminalSession *s) {
|
||||
int row_bytes = g_cols * (int)sizeof(CharCell);
|
||||
if (g_rows > 0) {
|
||||
scrollback_push_row(s, s->cells, g_cols);
|
||||
}
|
||||
memmove(s->cells, s->cells + g_cols, row_bytes * (g_rows - 1));
|
||||
for (int i = 0; i < g_cols; i++) {
|
||||
int idx = (g_rows - 1) * g_cols + i;
|
||||
s->cells[idx].c = ' ';
|
||||
s->cells[idx].color = s->fg_color;
|
||||
}
|
||||
s->cursor_row = g_rows - 1;
|
||||
s->cursor_col = 0;
|
||||
}
|
||||
|
||||
static void session_put_char(TerminalSession *s, char c) {
|
||||
if (c == '\n') {
|
||||
s->cursor_row++;
|
||||
s->cursor_col = 0;
|
||||
if (s->cursor_row >= g_rows) session_scroll(s);
|
||||
return;
|
||||
}
|
||||
if (c == '\r') {
|
||||
s->cursor_col = 0;
|
||||
return;
|
||||
}
|
||||
if (c == '\b') {
|
||||
if (s->cursor_col > 0) s->cursor_col--;
|
||||
int idx = s->cursor_row * g_cols + s->cursor_col;
|
||||
if (idx >= 0 && idx < g_cols * g_rows) {
|
||||
s->cells[idx].c = ' ';
|
||||
s->cells[idx].color = s->fg_color;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (c == '\t') {
|
||||
int next = ((s->cursor_col / 4) + 1) * 4;
|
||||
while (s->cursor_col < next) {
|
||||
session_put_char(s, ' ');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c < 32) return;
|
||||
|
||||
int idx = s->cursor_row * g_cols + s->cursor_col;
|
||||
if (idx >= 0 && idx < g_cols * g_rows) {
|
||||
s->cells[idx].c = c;
|
||||
s->cells[idx].color = s->fg_color;
|
||||
}
|
||||
s->cursor_col++;
|
||||
if (s->cursor_col >= g_cols) {
|
||||
s->cursor_col = 0;
|
||||
s->cursor_row++;
|
||||
if (s->cursor_row >= g_rows) session_scroll(s);
|
||||
}
|
||||
}
|
||||
|
||||
static void ansi_handle_sgr(TerminalSession *s) {
|
||||
if (s->ansi_param_count == 0) {
|
||||
session_reset_colors(s);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < s->ansi_param_count; i++) {
|
||||
int p = s->ansi_params[i];
|
||||
if (p == 0) session_reset_colors(s);
|
||||
else if (p >= 30 && p <= 37) s->fg_color = ansi_color_16(p - 30);
|
||||
else if (p >= 90 && p <= 97) s->fg_color = ansi_color_16(8 + (p - 90));
|
||||
else if (p >= 40 && p <= 47) s->bg_color = ansi_color_16(p - 40);
|
||||
else if (p >= 100 && p <= 107) s->bg_color = ansi_color_16(8 + (p - 100));
|
||||
else if (p == 38 || p == 48) {
|
||||
if (i + 4 < s->ansi_param_count && s->ansi_params[i + 1] == 2) {
|
||||
int r = s->ansi_params[i + 2] & 0xFF;
|
||||
int g = s->ansi_params[i + 3] & 0xFF;
|
||||
int b = s->ansi_params[i + 4] & 0xFF;
|
||||
uint32_t color = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
|
||||
if (p == 38) s->fg_color = color;
|
||||
else s->bg_color = color;
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ansi_finalize(TerminalSession *s, char cmd) {
|
||||
if (cmd == 'm') {
|
||||
ansi_handle_sgr(s);
|
||||
} else if (cmd == 'J') {
|
||||
int mode = s->ansi_param_count > 0 ? s->ansi_params[0] : 0;
|
||||
if (mode == 2 || mode == 0) session_clear(s);
|
||||
} else if (cmd == 'K') {
|
||||
int row = s->cursor_row;
|
||||
for (int col = s->cursor_col; col < g_cols; col++) {
|
||||
int idx = row * g_cols + col;
|
||||
s->cells[idx].c = ' ';
|
||||
s->cells[idx].color = s->fg_color;
|
||||
}
|
||||
} else if (cmd == 'H' || cmd == 'f') {
|
||||
int row = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
|
||||
int col = (s->ansi_param_count > 1) ? s->ansi_params[1] : 1;
|
||||
if (row < 1) row = 1;
|
||||
if (col < 1) col = 1;
|
||||
s->cursor_row = row - 1;
|
||||
s->cursor_col = col - 1;
|
||||
} else if (cmd == 'A') {
|
||||
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
|
||||
s->cursor_row -= n;
|
||||
if (s->cursor_row < 0) s->cursor_row = 0;
|
||||
} else if (cmd == 'B') {
|
||||
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
|
||||
s->cursor_row += n;
|
||||
if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1;
|
||||
} else if (cmd == 'C') {
|
||||
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
|
||||
s->cursor_col += n;
|
||||
if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1;
|
||||
} else if (cmd == 'D') {
|
||||
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
|
||||
s->cursor_col -= n;
|
||||
if (s->cursor_col < 0) s->cursor_col = 0;
|
||||
} else if (cmd == 's') {
|
||||
s->saved_row = s->cursor_row;
|
||||
s->saved_col = s->cursor_col;
|
||||
} else if (cmd == 'u') {
|
||||
s->cursor_row = s->saved_row;
|
||||
s->cursor_col = s->saved_col;
|
||||
}
|
||||
|
||||
s->ansi_state = 0;
|
||||
s->ansi_param_count = 0;
|
||||
}
|
||||
|
||||
static void session_process_char(TerminalSession *s, char c) {
|
||||
if (s->ansi_state == 0) {
|
||||
if (c == 27) {
|
||||
s->ansi_state = 1;
|
||||
s->ansi_param_count = 0;
|
||||
s->ansi_params[0] = 0;
|
||||
return;
|
||||
}
|
||||
session_put_char(s, c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->ansi_state == 1) {
|
||||
if (c == '[') {
|
||||
s->ansi_state = 2;
|
||||
s->ansi_param_count = 0;
|
||||
s->ansi_params[0] = 0;
|
||||
return;
|
||||
}
|
||||
s->ansi_state = 0;
|
||||
session_put_char(s, c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->ansi_state == 2) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
int idx = s->ansi_param_count;
|
||||
s->ansi_params[idx] = s->ansi_params[idx] * 10 + (c - '0');
|
||||
return;
|
||||
}
|
||||
if (c == ';') {
|
||||
if (s->ansi_param_count < 7) {
|
||||
s->ansi_param_count++;
|
||||
s->ansi_params[s->ansi_param_count] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
s->ansi_param_count++;
|
||||
ansi_finalize(s, c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void session_process_output(TerminalSession *s, const char *buf, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
session_process_char(s, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_tab_width(void) {
|
||||
if (g_tab_count <= 0) return g_win_w;
|
||||
int tab_w = g_win_w / g_tab_count;
|
||||
if (tab_w < 60) tab_w = 60;
|
||||
return tab_w;
|
||||
}
|
||||
|
||||
static int read_proc_field(int pid, const char *field, char *out, int max_len) {
|
||||
if (!out || max_len <= 0) return -1;
|
||||
char path[64];
|
||||
path[0] = 0;
|
||||
str_append(path, "/proc/", sizeof(path));
|
||||
char pid_buf[16];
|
||||
itoa(pid, pid_buf);
|
||||
str_append(path, pid_buf, sizeof(path));
|
||||
str_append(path, "/", sizeof(path));
|
||||
str_append(path, field, sizeof(path));
|
||||
|
||||
int fd = sys_open(path, "r");
|
||||
if (fd < 0) return -1;
|
||||
int bytes = sys_read(fd, out, max_len - 1);
|
||||
sys_close(fd);
|
||||
if (bytes <= 0) return -1;
|
||||
out[bytes] = 0;
|
||||
trim_end(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void truncate_label(const char *src, char *out, int max_chars) {
|
||||
if (!out || max_chars <= 0) return;
|
||||
int len = (int)strlen(src);
|
||||
if (len <= max_chars) {
|
||||
str_copy(out, src, max_chars + 1);
|
||||
return;
|
||||
}
|
||||
if (max_chars <= 3) {
|
||||
for (int i = 0; i < max_chars; i++) out[i] = src[i];
|
||||
out[max_chars] = 0;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < max_chars - 3; i++) out[i] = src[i];
|
||||
out[max_chars - 3] = '.';
|
||||
out[max_chars - 2] = '.';
|
||||
out[max_chars - 1] = '.';
|
||||
out[max_chars] = 0;
|
||||
}
|
||||
|
||||
static void get_tab_title(TerminalSession *s, char *out, int max_len) {
|
||||
if (!s || !out) return;
|
||||
int fg = sys_tty_get_fg(s->tty_id);
|
||||
if (fg > 0) {
|
||||
if (read_proc_field(fg, "name", out, max_len) == 0) return;
|
||||
}
|
||||
if (s->bsh_pid > 0) {
|
||||
if (read_proc_field(s->bsh_pid, "cwd", out, max_len) == 0) return;
|
||||
}
|
||||
str_copy(out, "Bsh", max_len);
|
||||
}
|
||||
|
||||
static int read_config_value(const char *key, char *out, int max_len) {
|
||||
if (!key || !out || max_len <= 0) return -1;
|
||||
int fd = sys_open("/Library/bsh/bshrc", "r");
|
||||
if (fd < 0) return -1;
|
||||
|
||||
char buf[4096];
|
||||
int bytes = sys_read(fd, buf, sizeof(buf) - 1);
|
||||
sys_close(fd);
|
||||
if (bytes <= 0) return -1;
|
||||
buf[bytes] = 0;
|
||||
|
||||
char *line = buf;
|
||||
while (*line) {
|
||||
char *end = line;
|
||||
while (*end && *end != '\n' && *end != '\r') end++;
|
||||
char saved = *end;
|
||||
*end = 0;
|
||||
|
||||
trim_end(line);
|
||||
if (line[0] != '#' && line[0] != 0) {
|
||||
char *sep = line;
|
||||
while (*sep && *sep != '=') sep++;
|
||||
if (*sep == '=') {
|
||||
*sep = 0;
|
||||
if (strcmp(line, key) == 0) {
|
||||
str_copy(out, sep + 1, max_len);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line = end + (saved ? 1 : 0);
|
||||
if (saved == '\r' && *line == '\n') line++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void resolve_bsh_path(char *out, int max_len) {
|
||||
char path_line[256];
|
||||
if (read_config_value("PATH", path_line, sizeof(path_line)) != 0) {
|
||||
str_copy(path_line, "/bin", sizeof(path_line));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int start = 0;
|
||||
while (1) {
|
||||
if (path_line[i] == ':' || path_line[i] == 0) {
|
||||
int len = i - start;
|
||||
if (len > 0) {
|
||||
char base[128];
|
||||
if (len >= (int)sizeof(base)) len = (int)sizeof(base) - 1;
|
||||
for (int j = 0; j < len; j++) base[j] = path_line[start + j];
|
||||
base[len] = 0;
|
||||
|
||||
char candidate[160];
|
||||
candidate[0] = 0;
|
||||
str_append(candidate, base, sizeof(candidate));
|
||||
if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate));
|
||||
str_append(candidate, "bsh.elf", sizeof(candidate));
|
||||
if (sys_exists(candidate)) {
|
||||
str_copy(out, candidate, max_len);
|
||||
return;
|
||||
}
|
||||
|
||||
candidate[0] = 0;
|
||||
str_append(candidate, base, sizeof(candidate));
|
||||
if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate));
|
||||
str_append(candidate, "bsh", sizeof(candidate));
|
||||
if (sys_exists(candidate)) {
|
||||
str_copy(out, candidate, max_len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
if (path_line[i] == 0) break;
|
||||
i++;
|
||||
}
|
||||
|
||||
str_copy(out, "/bin/bsh.elf", max_len);
|
||||
}
|
||||
|
||||
static bool has_space(const char *s) {
|
||||
if (!s) return false;
|
||||
while (*s) {
|
||||
if (*s == ' ') return true;
|
||||
s++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void terminal_resize(int w, int h) {
|
||||
int min_w = CHAR_W * 40;
|
||||
int min_h = TAB_BAR_H + (g_line_h * 10);
|
||||
if (w < min_w) w = min_w;
|
||||
if (h < min_h) h = min_h;
|
||||
|
||||
g_win_w = w;
|
||||
g_win_h = h;
|
||||
|
||||
int new_cols = w / CHAR_W;
|
||||
int content_h = h - TAB_BAR_H - CONTENT_PAD_BOTTOM;
|
||||
if (g_line_h <= 0) g_line_h = DEFAULT_LINE_H;
|
||||
if (content_h < g_line_h) content_h = g_line_h;
|
||||
int new_rows = content_h / g_line_h;
|
||||
if (new_cols < 10) new_cols = 10;
|
||||
if (new_rows < 5) new_rows = 5;
|
||||
|
||||
if (new_cols == g_cols && new_rows == g_rows) return;
|
||||
|
||||
int old_cols = g_cols;
|
||||
int old_rows = g_rows;
|
||||
g_cols = new_cols;
|
||||
g_rows = new_rows;
|
||||
|
||||
for (int i = 0; i < g_tab_count; i++) {
|
||||
TerminalSession *s = &g_tabs[i];
|
||||
int old_scroll_count = s->scroll_count;
|
||||
int old_scroll_offset = s->scroll_offset;
|
||||
int old_total_lines = old_scroll_count + old_rows;
|
||||
int old_bottom_line = old_total_lines - 1 - old_scroll_offset;
|
||||
int old_top_line = old_bottom_line - (old_rows - 1);
|
||||
CharCell *old_cells = s->cells;
|
||||
int old_cursor_row = s->cursor_row;
|
||||
int old_cursor_col = s->cursor_col;
|
||||
|
||||
s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows);
|
||||
for (int j = 0; j < g_cols * g_rows; j++) {
|
||||
s->cells[j].c = ' ';
|
||||
s->cells[j].color = s->fg_color;
|
||||
}
|
||||
|
||||
int row_start = 0;
|
||||
if (old_rows > g_rows) {
|
||||
if (old_cursor_row >= g_rows) {
|
||||
row_start = old_cursor_row - (g_rows - 1);
|
||||
} else {
|
||||
row_start = 0;
|
||||
}
|
||||
int max_start = old_rows - g_rows;
|
||||
if (row_start > max_start) row_start = max_start;
|
||||
if (row_start < 0) row_start = 0;
|
||||
}
|
||||
int dropped = 0;
|
||||
if (row_start > 0) {
|
||||
int projected = old_scroll_count + row_start;
|
||||
if (projected > SCROLLBACK_LINES) dropped = projected - SCROLLBACK_LINES;
|
||||
}
|
||||
int desired_top_line = old_top_line - dropped;
|
||||
if (desired_top_line < 0) desired_top_line = 0;
|
||||
for (int r = 0; r < row_start; r++) {
|
||||
scrollback_push_row(s, old_cells + (r * old_cols), old_cols);
|
||||
}
|
||||
|
||||
int copy_rows = old_rows;
|
||||
if (copy_rows > g_rows) copy_rows = g_rows;
|
||||
int copy_cols = old_cols < g_cols ? old_cols : g_cols;
|
||||
for (int r = 0; r < copy_rows; r++) {
|
||||
CharCell *src = old_cells + ((row_start + r) * old_cols);
|
||||
CharCell *dst = s->cells + (r * g_cols);
|
||||
for (int c = 0; c < copy_cols; c++) {
|
||||
dst[c] = src[c];
|
||||
}
|
||||
}
|
||||
|
||||
s->cursor_row = old_cursor_row - row_start;
|
||||
if (old_rows <= g_rows) s->cursor_row = old_cursor_row;
|
||||
if (s->cursor_row < 0) s->cursor_row = 0;
|
||||
if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1;
|
||||
|
||||
s->cursor_col = old_cursor_col;
|
||||
if (s->cursor_col < 0) s->cursor_col = 0;
|
||||
if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1;
|
||||
|
||||
if (old_scroll_offset == 0) {
|
||||
s->scroll_offset = 0;
|
||||
} else {
|
||||
int new_total_lines = s->scroll_count + g_rows;
|
||||
int desired_bottom = desired_top_line + (g_rows - 1);
|
||||
s->scroll_offset = (new_total_lines - 1) - desired_bottom;
|
||||
}
|
||||
session_adjust_scroll(s, 0);
|
||||
|
||||
if (old_cells) free(old_cells);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_tabs(void) {
|
||||
if (g_tab_count <= 0) return;
|
||||
ui_draw_rect(g_win, 0, 0, g_win_w, TAB_BAR_H, 0xFF1A1A1A);
|
||||
int tab_w = get_tab_width();
|
||||
for (int i = 0; i < g_tab_count; i++) {
|
||||
int x = i * tab_w;
|
||||
uint32_t bg = (i == g_active_tab) ? 0xFF333333 : 0xFF222222;
|
||||
ui_draw_rect(g_win, x, 0, tab_w - 2, TAB_BAR_H, bg);
|
||||
int close_size = TAB_CLOSE_W;
|
||||
int close_x = x + tab_w - TAB_CLOSE_PAD - close_size;
|
||||
int close_y = (TAB_BAR_H - close_size) / 2;
|
||||
uint32_t close_bg = (i == g_active_tab) ? 0xFF444444 : 0xFF333333;
|
||||
ui_draw_rect(g_win, close_x, close_y, close_size, close_size, close_bg);
|
||||
ui_draw_string(g_win, close_x + 2, close_y + 1, "x", 0xFFFFFFFF);
|
||||
char title[64];
|
||||
char label[64];
|
||||
get_tab_title(&g_tabs[i], title, sizeof(title));
|
||||
int text_w = tab_w - 10 - (TAB_CLOSE_PAD + TAB_CLOSE_W);
|
||||
int max_chars = text_w / CHAR_W;
|
||||
if (max_chars < 4) max_chars = 4;
|
||||
truncate_label(title, label, max_chars);
|
||||
ui_draw_string(g_win, x + 6, 4, label, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_session(TerminalSession *s) {
|
||||
int base_y = TAB_BAR_H;
|
||||
ui_draw_rect(g_win, 0, base_y, g_win_w, g_win_h - base_y, s->bg_color);
|
||||
|
||||
int max_offset = scrollback_max_offset(s);
|
||||
if (s->scroll_offset > max_offset) s->scroll_offset = max_offset;
|
||||
int total_lines = s->scroll_count + g_rows;
|
||||
int bottom_line = total_lines - 1 - s->scroll_offset;
|
||||
int top_line = bottom_line - (g_rows - 1);
|
||||
|
||||
for (int row = 0; row < g_rows; row++) {
|
||||
int line_index = top_line + row;
|
||||
CharCell *line = NULL;
|
||||
int line_cols = 0;
|
||||
if (line_index >= 0 && line_index < s->scroll_count) {
|
||||
line = scrollback_get_line(s, line_index, &line_cols);
|
||||
} else if (line_index >= s->scroll_count && line_index < total_lines) {
|
||||
int live_row = line_index - s->scroll_count;
|
||||
if (live_row >= 0 && live_row < g_rows) {
|
||||
line = s->cells + (live_row * g_cols);
|
||||
line_cols = g_cols;
|
||||
}
|
||||
}
|
||||
for (int col = 0; col < g_cols; col++) {
|
||||
char ch = ' ';
|
||||
uint32_t color = s->fg_color;
|
||||
if (line && col < line_cols) {
|
||||
ch = line[col].c;
|
||||
if (ch == 0) ch = ' ';
|
||||
color = line[col].color;
|
||||
}
|
||||
char str[2] = { ch, 0 };
|
||||
int x = col * CHAR_W;
|
||||
int y = base_y + row * g_line_h;
|
||||
ui_draw_string_bitmap(g_win, x, y, str, color);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->scroll_offset == 0) {
|
||||
int cx = s->cursor_col * CHAR_W;
|
||||
int cy = base_y + s->cursor_row * g_line_h;
|
||||
ui_draw_rect(g_win, cx, cy + g_line_h - 2, CHAR_W, 2, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
ui_mark_dirty(g_win, 0, 0, g_win_w, g_win_h);
|
||||
}
|
||||
|
||||
static void tab_init(TerminalSession *s, int tty_id, int bsh_pid) {
|
||||
s->tty_id = tty_id;
|
||||
s->bsh_pid = bsh_pid;
|
||||
s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows);
|
||||
s->scrollback = (CharCell *)malloc(sizeof(CharCell) * SCROLLBACK_LINES * SCROLLBACK_COLS);
|
||||
s->scroll_cols = (int *)malloc(sizeof(int) * SCROLLBACK_LINES);
|
||||
s->cursor_row = 0;
|
||||
s->cursor_col = 0;
|
||||
s->ansi_state = 0;
|
||||
s->ansi_param_count = 0;
|
||||
s->saved_row = 0;
|
||||
s->saved_col = 0;
|
||||
session_reset_colors(s);
|
||||
scrollback_init(s);
|
||||
session_clear(s);
|
||||
}
|
||||
|
||||
static void tab_free(TerminalSession *s) {
|
||||
if (!s) return;
|
||||
if (s->cells) {
|
||||
free(s->cells);
|
||||
s->cells = NULL;
|
||||
}
|
||||
if (s->scrollback) {
|
||||
free(s->scrollback);
|
||||
s->scrollback = NULL;
|
||||
}
|
||||
if (s->scroll_cols) {
|
||||
free(s->scroll_cols);
|
||||
s->scroll_cols = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void close_tab(int idx) {
|
||||
if (idx < 0 || idx >= g_tab_count) return;
|
||||
TerminalSession *s = &g_tabs[idx];
|
||||
|
||||
sys_tty_kill_all(s->tty_id);
|
||||
sys_tty_destroy(s->tty_id);
|
||||
|
||||
tab_free(s);
|
||||
|
||||
for (int i = idx; i < g_tab_count - 1; i++) {
|
||||
g_tabs[i] = g_tabs[i + 1];
|
||||
}
|
||||
g_tab_count--;
|
||||
if (g_tab_count <= 0) {
|
||||
sys_exit(0);
|
||||
}
|
||||
if (g_active_tab > idx) {
|
||||
g_active_tab--;
|
||||
} else if (g_active_tab == idx) {
|
||||
if (g_active_tab >= g_tab_count) g_active_tab = g_tab_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int create_tab(void) {
|
||||
if (g_tab_count >= MAX_TABS) return -1;
|
||||
|
||||
int tty_id = sys_tty_create();
|
||||
if (tty_id < 0) return -1;
|
||||
|
||||
char start_dir[256];
|
||||
start_dir[0] = 0;
|
||||
if (g_tab_count > 0) {
|
||||
TerminalSession *active = &g_tabs[g_active_tab];
|
||||
if (active->bsh_pid > 0) {
|
||||
read_proc_field(active->bsh_pid, "cwd", start_dir, sizeof(start_dir));
|
||||
}
|
||||
}
|
||||
|
||||
char args[32];
|
||||
args[0] = 0;
|
||||
str_append(args, "-t ", sizeof(args));
|
||||
char id_buf[8];
|
||||
itoa(tty_id, id_buf);
|
||||
str_append(args, id_buf, sizeof(args));
|
||||
|
||||
if (start_dir[0]) {
|
||||
str_append(args, " -d ", sizeof(args));
|
||||
if (has_space(start_dir)) {
|
||||
str_append(args, "\"", sizeof(args));
|
||||
str_append(args, start_dir, sizeof(args));
|
||||
str_append(args, "\"", sizeof(args));
|
||||
} else {
|
||||
str_append(args, start_dir, sizeof(args));
|
||||
}
|
||||
}
|
||||
|
||||
char bsh_path[128];
|
||||
resolve_bsh_path(bsh_path, sizeof(bsh_path));
|
||||
int pid = sys_spawn(bsh_path, args, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_TTY_ID, tty_id);
|
||||
if (pid < 0) return -1;
|
||||
|
||||
tab_init(&g_tabs[g_tab_count], tty_id, pid);
|
||||
g_tab_count++;
|
||||
return g_tab_count - 1;
|
||||
}
|
||||
|
||||
static void handle_key(gui_event_t *ev) {
|
||||
TerminalSession *s = &g_tabs[g_active_tab];
|
||||
char c = (char)ev->arg1;
|
||||
bool ctrl = ev->arg3 != 0;
|
||||
|
||||
if (ctrl && c == 't') {
|
||||
int idx = create_tab();
|
||||
if (idx >= 0) g_active_tab = idx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctrl && c == 20) {
|
||||
if (g_tab_count > 0) g_active_tab = (g_active_tab + 1) % g_tab_count;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctrl && c == 19) {
|
||||
if (g_tab_count > 0) g_active_tab = (g_active_tab + g_tab_count - 1) % g_tab_count;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctrl && (c == 'c' || c == 'C')) {
|
||||
int fg = sys_tty_get_fg(s->tty_id);
|
||||
if (fg > 0) {
|
||||
sys_tty_kill_fg(s->tty_id);
|
||||
sys_tty_set_fg(s->tty_id, 0);
|
||||
return;
|
||||
}
|
||||
char ch = 3;
|
||||
sys_tty_write_in(s->tty_id, &ch, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
sys_tty_write_in(s->tty_id, &c, 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
g_win = ui_window_create("Terminal", 60, 60, g_win_w, g_win_h);
|
||||
ui_window_set_resizable(g_win, true);
|
||||
|
||||
int fh = (int)ui_get_font_height();
|
||||
if (fh > 0) g_line_h = fh;
|
||||
terminal_resize(g_win_w, g_win_h);
|
||||
|
||||
int idx = create_tab();
|
||||
if (idx < 0) return 1;
|
||||
g_active_tab = idx;
|
||||
|
||||
gui_event_t ev;
|
||||
char out_buf[TTY_READ_CHUNK];
|
||||
|
||||
while (1) {
|
||||
bool dirty = false;
|
||||
|
||||
for (int i = 0; i < g_tab_count; i++) {
|
||||
TerminalSession *s = &g_tabs[i];
|
||||
int read = 0;
|
||||
while ((read = sys_tty_read_out(s->tty_id, out_buf, sizeof(out_buf))) > 0) {
|
||||
session_process_output(s, out_buf, read);
|
||||
if (i == g_active_tab) dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (ui_get_event(g_win, &ev)) {
|
||||
if (ev.type == GUI_EVENT_CLOSE) {
|
||||
for (int i = 0; i < g_tab_count; i++) {
|
||||
if (g_tabs[i].bsh_pid > 0) sys_kill(g_tabs[i].bsh_pid);
|
||||
}
|
||||
sys_exit(0);
|
||||
} else if (ev.type == GUI_EVENT_KEY) {
|
||||
handle_key(&ev);
|
||||
dirty = true;
|
||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
||||
if (ev.arg2 < TAB_BAR_H) {
|
||||
int tab_w = get_tab_width();
|
||||
int tab = ev.arg1 / tab_w;
|
||||
if (tab >= 0 && tab < g_tab_count) {
|
||||
int close_size = TAB_CLOSE_W;
|
||||
int close_x = tab * tab_w + tab_w - TAB_CLOSE_PAD - close_size;
|
||||
int close_y = (TAB_BAR_H - close_size) / 2;
|
||||
if (ev.arg1 >= close_x && ev.arg1 < close_x + close_size &&
|
||||
ev.arg2 >= close_y && ev.arg2 < close_y + close_size) {
|
||||
close_tab(tab);
|
||||
} else {
|
||||
g_active_tab = tab;
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
} else if (ev.type == GUI_EVENT_MOUSE_WHEEL) {
|
||||
int lines = ev.arg1 * 3;
|
||||
if (lines != 0) {
|
||||
session_adjust_scroll(&g_tabs[g_active_tab], lines);
|
||||
dirty = true;
|
||||
}
|
||||
} else if (ev.type == GUI_EVENT_RESIZE) {
|
||||
terminal_resize(ev.arg1, ev.arg2);
|
||||
dirty = true;
|
||||
} else if (ev.type == GUI_EVENT_PAINT) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
draw_tabs();
|
||||
draw_session(&g_tabs[g_active_tab]);
|
||||
} else {
|
||||
sys_yield();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -40,6 +40,46 @@ static void editor_strcpy(char *dest, const char *src) {
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
static int editor_strlen(const char *s) {
|
||||
int len = 0;
|
||||
while (s && s[len]) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void editor_strncat(char *dst, const char *src, int max_len) {
|
||||
if (!dst || !src || max_len <= 0) return;
|
||||
int dlen = editor_strlen(dst);
|
||||
int i = 0;
|
||||
while (dlen + i < max_len - 1 && src[i]) {
|
||||
dst[dlen + i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[dlen + i] = 0;
|
||||
}
|
||||
|
||||
static void editor_resolve_path(const char *input, char *out, int max_len) {
|
||||
if (!out || max_len <= 0) return;
|
||||
if (!input || input[0] == 0) {
|
||||
out[0] = 0;
|
||||
return;
|
||||
}
|
||||
if (input[0] == '/') {
|
||||
editor_strcpy(out, input);
|
||||
return;
|
||||
}
|
||||
|
||||
char cwd[256];
|
||||
if (sys_getcwd(cwd, sizeof(cwd)) < 0) {
|
||||
editor_strcpy(out, input);
|
||||
return;
|
||||
}
|
||||
|
||||
editor_strcpy(out, cwd);
|
||||
int len = editor_strlen(out);
|
||||
if (len > 0 && out[len - 1] != '/') editor_strncat(out, "/", max_len);
|
||||
editor_strncat(out, input, max_len);
|
||||
}
|
||||
|
||||
static void editor_clear_all(void) {
|
||||
for (int i = 0; i < EDITOR_MAX_LINES; i++) {
|
||||
for (int j = 0; j < EDITOR_MAX_LINE_LEN; j++) {
|
||||
@@ -71,9 +111,11 @@ static void editor_ensure_cursor_visible(void) {
|
||||
|
||||
void editor_open_file(const char *filename) {
|
||||
editor_clear_all();
|
||||
editor_strcpy(open_filename, filename);
|
||||
char resolved[256];
|
||||
editor_resolve_path(filename, resolved, sizeof(resolved));
|
||||
editor_strcpy(open_filename, resolved);
|
||||
|
||||
int fd = sys_open(filename, "r");
|
||||
int fd = sys_open(resolved, "r");
|
||||
if (fd < 0) {
|
||||
file_modified = 0;
|
||||
return;
|
||||
|
||||
@@ -41,6 +41,40 @@ static int viewer_strlen(const char *s) {
|
||||
return len;
|
||||
}
|
||||
|
||||
static void viewer_strncat(char *dst, const char *src, int max_len) {
|
||||
if (!dst || !src || max_len <= 0) return;
|
||||
int dlen = viewer_strlen(dst);
|
||||
int i = 0;
|
||||
while (dlen + i < max_len - 1 && src[i]) {
|
||||
dst[dlen + i] = src[i];
|
||||
i++;
|
||||
}
|
||||
dst[dlen + i] = 0;
|
||||
}
|
||||
|
||||
static void viewer_resolve_path(const char *input, char *out, int max_len) {
|
||||
if (!out || max_len <= 0) return;
|
||||
if (!input || input[0] == 0) {
|
||||
out[0] = 0;
|
||||
return;
|
||||
}
|
||||
if (input[0] == '/') {
|
||||
viewer_strcpy(out, input);
|
||||
return;
|
||||
}
|
||||
|
||||
char cwd[256];
|
||||
if (sys_getcwd(cwd, sizeof(cwd)) < 0) {
|
||||
viewer_strcpy(out, input);
|
||||
return;
|
||||
}
|
||||
|
||||
viewer_strcpy(out, cwd);
|
||||
int len = viewer_strlen(out);
|
||||
if (len > 0 && out[len - 1] != '/') viewer_strncat(out, "/", max_len);
|
||||
viewer_strncat(out, input, max_len);
|
||||
}
|
||||
|
||||
static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h,
|
||||
uint32_t *dst, int dst_w, int dst_h) {
|
||||
if (src_w == dst_w && src_h == dst_h) {
|
||||
@@ -141,7 +175,9 @@ static void viewer_paint(ui_window_t win) {
|
||||
}
|
||||
|
||||
void viewer_open_file(const char *path) {
|
||||
int fd = sys_open(path, "r");
|
||||
char resolved[256];
|
||||
viewer_resolve_path(path, resolved, sizeof(resolved));
|
||||
int fd = sys_open(resolved, "r");
|
||||
if (fd < 0) return;
|
||||
|
||||
uint32_t file_size = sys_size(fd);
|
||||
@@ -250,13 +286,13 @@ void viewer_open_file(const char *path) {
|
||||
stbi_image_free(rgba);
|
||||
free(buf);
|
||||
|
||||
viewer_strcpy(viewer_file_path, path);
|
||||
viewer_strcpy(viewer_file_path, resolved);
|
||||
|
||||
const char *fname = path;
|
||||
int plen = viewer_strlen(path);
|
||||
const char *fname = resolved;
|
||||
int plen = viewer_strlen(resolved);
|
||||
for (int i = plen - 1; i >= 0; i--) {
|
||||
if (path[i] == '/') {
|
||||
fname = &path[i + 1];
|
||||
if (resolved[i] == '/') {
|
||||
fname = &resolved[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
157
src/userland/libc/libmath.c
Normal file
157
src/userland/libc/libmath.c
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||
// 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 "math.h"
|
||||
static double _pow_int(double b, int e) {
|
||||
if (e == 0) return 1.0;
|
||||
if (e < 0) { return 1.0 / _pow_int(b, -e); }
|
||||
double r = 1.0;
|
||||
while (e > 0) {
|
||||
if (e & 1) r *= b;
|
||||
b *= b;
|
||||
e >>= 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
double fabs(double x) {
|
||||
return x < 0.0 ? -x : x;
|
||||
}
|
||||
double fmod(double x, double y) {
|
||||
if (y == 0.0) return 0.0;
|
||||
return x - (int)(x / y) * y;
|
||||
}
|
||||
|
||||
double floor(double x) {
|
||||
int i = (int)x;
|
||||
return (x < 0.0 && (double)i != x) ? (double)(i - 1) : (double)i;
|
||||
}
|
||||
|
||||
double ceil(double x) {
|
||||
int i = (int)x;
|
||||
return (x > 0.0 && (double)i != x) ? (double)(i + 1) : (double)i;
|
||||
}
|
||||
|
||||
double sin(double x) {
|
||||
x = fmod(x, 2.0 * M_PI);
|
||||
if (x > M_PI) x -= 2.0 * M_PI;
|
||||
if (x < -M_PI) x += 2.0 * M_PI;
|
||||
|
||||
double x2 = x * x;
|
||||
double term = x;
|
||||
double sum = x;
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
term *= -x2 / ((2*i) * (2*i + 1));
|
||||
sum += term;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
double cos(double x) {
|
||||
return sin(x + M_PI / 2.0);
|
||||
}
|
||||
|
||||
double tan(double x) {
|
||||
double c = cos(x);
|
||||
if (fabs(c) < 1e-10) return 1e15;
|
||||
return sin(x) / c;
|
||||
}
|
||||
|
||||
double sqrt(double x) {
|
||||
if (x <= 0.0) return 0.0;
|
||||
double g = x * 0.5;
|
||||
for (int i = 0; i < 25; i++) {
|
||||
g = (g + x / g) * 0.5;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
double log(double x) {
|
||||
if (x <= 0.0) return -1e30;
|
||||
|
||||
int e = 0;
|
||||
while (x > 2.0) { x /= 2.0; e++; }
|
||||
while (x < 0.5) { x *= 2.0; e--; }
|
||||
|
||||
double t = (x - 1.0) / (x + 1.0);
|
||||
double t2 = t * t;
|
||||
double sum = t, term = t;
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
term *= t2;
|
||||
sum += term / (2*i + 1);
|
||||
}
|
||||
return 2.0 * sum + e * M_LN2;
|
||||
}
|
||||
|
||||
double log2(double x) {
|
||||
// log2(x) = ln(x) / ln(2)
|
||||
return log(x) / M_LN2;
|
||||
}
|
||||
|
||||
double log10(double x) {
|
||||
return log(x) / 2.302585092994046;
|
||||
}
|
||||
|
||||
double exp(double x) {
|
||||
if (x > 700.0) return 1e300;
|
||||
if (x < -700.0) return 0.0;
|
||||
|
||||
int k = (int)(x / M_LN2);
|
||||
if (x < 0.0 && (double)k * M_LN2 > x) k--;
|
||||
double r = x - (double)k * M_LN2;
|
||||
|
||||
double sum = 1.0, term = 1.0;
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
term *= r / (double)i;
|
||||
sum += term;
|
||||
}
|
||||
|
||||
double result = sum;
|
||||
if (k >= 0) { for (int i = 0; i < k; i++) result *= 2.0; }
|
||||
else { for (int i = 0; i < -k; i++) result /= 2.0; }
|
||||
return result;
|
||||
}
|
||||
double pow(double base, double exponent) {
|
||||
if (base == 0.0) return 0.0;
|
||||
if (exponent == 0.0) return 1.0;
|
||||
int ie = (int)exponent;
|
||||
if ((double)ie == exponent) return _pow_int(base, ie);
|
||||
|
||||
if (base < 0.0) return 0.0;
|
||||
return exp(exponent * log(base));
|
||||
}
|
||||
|
||||
double sinh(double x) {
|
||||
double ep = exp(x);
|
||||
double em = exp(-x);
|
||||
return (ep - em) * 0.5;
|
||||
}
|
||||
|
||||
double cosh(double x) {
|
||||
double ep = exp(x);
|
||||
double em = exp(-x);
|
||||
return (ep + em) * 0.5;
|
||||
}
|
||||
|
||||
double tanh(double x) {
|
||||
double e2 = exp(2.0 * x);
|
||||
return (e2 - 1.0) / (e2 + 1.0);
|
||||
}
|
||||
|
||||
double hypot(double x, double y) {
|
||||
return sqrt(x*x + y*y);
|
||||
}
|
||||
|
||||
double fmin(double a, double b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
double fmax(double a, double b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
double fclamp(double x, double lo, double hi) {
|
||||
if (x < lo) return lo;
|
||||
if (x > hi) return hi;
|
||||
return x;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user