mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-05-30 10:26:59 +00:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e738041020 | ||
|
|
9830b6ad96 | ||
|
|
a09103e40d | ||
|
|
aff3e99ab2 | ||
|
|
93bf2e1734 | ||
|
|
eaa02c9a5d | ||
|
|
0ad151d7fc | ||
|
|
b54e371f3f | ||
|
|
79eeaa73d9 | ||
|
|
481eb42268 | ||
|
|
feb0d6ffbf | ||
|
|
67ebcb98d1 | ||
|
|
957c74365c | ||
|
|
31b6f48a2c | ||
|
|
67b7bb1a97 | ||
|
|
e05ff65f92 | ||
|
|
40f63097e1 | ||
|
|
d677d37b1c | ||
|
|
9b6297c917 | ||
|
|
dd6cbf1fe0 | ||
|
|
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 | ||
|
|
c6d512b0f2 | ||
|
|
0b7a134282 | ||
|
|
91b67bd8d5 | ||
|
|
e60f232812 | ||
|
|
3169ec51cb | ||
|
|
beb2c724ff | ||
|
|
bf3c2cb578 | ||
|
|
823e9c0ce7 | ||
|
|
0ddb1e7610 | ||
|
|
32a6bb4d72 | ||
|
|
d8e680604c | ||
|
|
2e28f860cb | ||
|
|
9634ebb086 | ||
|
|
d7d97b5a97 | ||
|
|
4a3752583c | ||
|
|
9de8ee143c | ||
|
|
8d5fa53d3e | ||
|
|
ad8db32305 | ||
|
|
92928e55fb | ||
|
|
31eb7afdc6 | ||
|
|
ad9fac3e28 | ||
|
|
70cd296d19 | ||
|
|
b7020152c1 | ||
|
|
63749b8734 | ||
|
|
4e8ea5acd2 | ||
|
|
5c199e028a | ||
|
|
ec2a9d1883 | ||
|
|
4c46650c64 | ||
|
|
1ee2fcad9e | ||
|
|
5c29ac1473 | ||
|
|
81743261bf | ||
|
|
4eeb907342 | ||
|
|
e527f63af7 | ||
|
|
1e19963a8d | ||
|
|
60ab70a49d | ||
|
|
d9bcc4aff7 | ||
|
|
5604866882 | ||
|
|
e95c82b162 | ||
|
|
9fb307e603 | ||
|
|
a7c3cccce7 | ||
|
|
7eb55f3a59 | ||
|
|
72baf6506d | ||
|
|
2817ad51da | ||
|
|
5b10127e02 | ||
|
|
1404a6ae4f | ||
|
|
7b7f134e27 | ||
|
|
0491c4ad0f | ||
|
|
c6fe9971d8 | ||
|
|
88f178e368 | ||
|
|
d824b4610a | ||
|
|
a4f16b0604 | ||
|
|
83413fdd2b | ||
|
|
2a918bc7ba | ||
|
|
2624b4f8df |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,3 +30,4 @@ limine
|
||||
.DS_Store
|
||||
/build/
|
||||
*.o
|
||||
disk.img
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -3,7 +3,7 @@
|
||||
|
||||
Copyright(C) Chris (boreddevnl) 2024-2026
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/\>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
124
Makefile
124
Makefile
@@ -132,58 +132,61 @@ $(KERNEL_ELF): $(OBJ_FILES)
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
||||
$(MAKE) -C $(SRC_DIR)/userland
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||
$(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
|
||||
rm -rf $(BUILD_DIR)/initrd
|
||||
mkdir -p $(BUILD_DIR)/initrd/bin
|
||||
mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers
|
||||
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 \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/bin/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; fi \
|
||||
done
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; fi \
|
||||
done
|
||||
@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 \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \
|
||||
cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \
|
||||
fi \
|
||||
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 *
|
||||
|
||||
$(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
|
||||
rm -rf $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)
|
||||
mkdir -p $(ISO_DIR)/EFI/BOOT
|
||||
|
||||
cp $(KERNEL_ELF) $(ISO_DIR)/
|
||||
cp limine.conf $(ISO_DIR)/
|
||||
mkdir -p $(ISO_DIR)/bin
|
||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/bin/; \
|
||||
echo " module_path: boot():/bin/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
||||
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then \
|
||||
mkdir -p $(ISO_DIR)/Library/DOOM; \
|
||||
cp $(SRC_DIR)/userland/games/doom/doom1.wad $(ISO_DIR)/Library/DOOM/; \
|
||||
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/
|
||||
echo " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
||||
@for f in $(SRC_DIR)/images/wallpapers/*; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/Wallpapers/; \
|
||||
echo " module_path: boot():/Library/images/Wallpapers/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/images/gif
|
||||
@for f in $(SRC_DIR)/images/gif/*.gif; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/images/gif/; \
|
||||
echo " module_path: boot():/Library/images/gif/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
mkdir -p $(ISO_DIR)/docs
|
||||
@for f in $$(find docs -name '*.md'); do \
|
||||
if [ -f "$$f" ]; then \
|
||||
dir=$$(dirname "$$f"); \
|
||||
mkdir -p $(ISO_DIR)/"$$dir"; \
|
||||
cp "$$f" $(ISO_DIR)/"$$dir"/; \
|
||||
echo " module_path: boot():/$$f" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
cp limine/limine-bios.sys $(ISO_DIR)/
|
||||
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
||||
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
|
||||
@@ -191,25 +194,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
||||
|
||||
mkdir -p $(ISO_DIR)/Library/Fonts
|
||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||
if [ -f "$$f" ]; then \
|
||||
basename=$$(basename "$$f"); \
|
||||
cp "$$f" $(ISO_DIR)/Library/Fonts/; \
|
||||
echo " module_path: boot():/Library/Fonts/$$basename" >> $(ISO_DIR)/limine.conf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
@if [ -f README.md ]; then \
|
||||
cp README.md $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/README.md" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
@if [ -f LICENSE ]; then \
|
||||
cp LICENSE $(ISO_DIR)/; \
|
||||
echo " module_path: boot():/LICENSE" >> $(ISO_DIR)/limine.conf; \
|
||||
fi
|
||||
|
||||
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
--efi-boot limine-uefi-cd.bin \
|
||||
@@ -222,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
|
||||
|
||||
|
||||
136
README.md
136
README.md
@@ -2,96 +2,92 @@
|
||||
|
||||
<div align="center">
|
||||
<img src="boredos.svg" alt="BoredOS Logo" width="450" />
|
||||
</div>
|
||||
BoredOS is a simple x86_64 hobbyist operating system.
|
||||
It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more!
|
||||
<p><em>A modern x86_64 hobbyist operating system built from the ground up.</em></p>
|
||||
|
||||

|
||||
*this screenshot might be outdated*
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||

|
||||

|
||||
[](https://buymeacoffee.com/boreddevhq)
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
BoredOS is a x86_64 operating system featuring a custom Desktop Environment (DE), a dedicated Window Manager (BoredWM), and a FAT32 filesystem. It balances low-level kernel exploration with a surprisingly capable userspace.
|
||||
|
||||

|
||||
> [!NOTE]
|
||||
> *The screenshot above may represent a previous build and is subject to change as the UI evolves.*
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
- userspace
|
||||
- JPG image support
|
||||
- Disk manager
|
||||
- Drag and drop mouse centered UI
|
||||
- Customizable UI
|
||||
- Basic Networking Stack
|
||||
- Bored WM
|
||||
- FAT32 filesystem
|
||||
- 64-bit long mode support
|
||||
- Multiboot2 compliant
|
||||
- Text editor
|
||||
- Markdown Viewer
|
||||
- Minesweeper
|
||||
- Markdown Viewer
|
||||
- GUI Text editor
|
||||
- Paint application
|
||||
- IDT
|
||||
- Ability to run on actual x86_64 hardware
|
||||
- CLI
|
||||
- (Limited) C Compiler
|
||||
|
||||
## Documentation
|
||||
### 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.
|
||||
* **SMP-Safe Spinlocks:** Robust kernel-wide synchronization for VFS, process management, and the GUI.
|
||||
* **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 and a basic web browser.
|
||||
|
||||
BoredOS has comprehensive documentation available in the [`docs/`](docs/) directory covering architecture, the build system, and application development SDKs.
|
||||
### 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. (PNG, GIF, JPEG, TGA, BMP)
|
||||
|
||||
- **[Index / Table of Contents](docs/README.md)**
|
||||
- **[Architecture Overview](docs/architecture/core.md)**
|
||||
- **[Building and Running](docs/build/usage.md)**
|
||||
- **[Application Development Guide](docs/appdev/custom_apps.md)**
|
||||
### 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.
|
||||
* **Games:** Minesweeper and DOOM.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Explore the internal workings of BoredOS via our comprehensive guides in the [`docs/`](docs/) directory.
|
||||
|
||||
* 📖 **[Documentation Index](docs/README.md)** – Start here.
|
||||
* 🏗️ **[Architecture Overview](docs/architecture/core.md)** – Deep dive into the kernel.
|
||||
* 🔨 **[Building and Running](docs/build/usage.md)** – Setup your build environment.
|
||||
* 🚀 **[AppDev SDK](docs/appdev/custom_apps.md)** – Build your own apps for BoredOS.
|
||||
|
||||
###
|
||||
###
|
||||
---
|
||||
|
||||
<h2 align="left">Help me brew some coffee! ☕️</h2>
|
||||
## Support the Journey
|
||||
|
||||
###
|
||||
If you find this project interesting or helpful, consider fueling the development with a coffee!
|
||||
|
||||
<p align="left">
|
||||
If you enjoy this project, and like what i'm doing here, consider buying me a coffee!
|
||||
<br><br>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
###
|
||||
|
||||
|
||||
## This project was previously labeled as "BrewKernel"
|
||||
Brewkernel was a text only very simple (and messy) project i started 3 years ago. It was my first work in OSDev and i absolutely loved it. It sadly just got too messy and i myself couldn't understand my own code anymore. About a year ago i started work on BoredOS, and pushed a *"working"* version of it a few days ago as of writing this *(Feb. 10 2026)*
|
||||
Brewkernel has already been deprecated and will not be accepting any pull requests or fix any issues as it is now a public archive.
|
||||
Thanks to everyone who helped me with Brewkernel, even if it were just ideas, and intend to keep working on this for the forseeable future!
|
||||
---
|
||||
|
||||
## ⚠️ Project Disclaimer & Heritage
|
||||
|
||||
**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 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.
|
||||
|
||||
---
|
||||
|
||||
## Contributors
|
||||
|
||||
- **boreddevnl** — Project creator and lead maintainer.
|
||||
- **Lluciocc** — Contributor.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2024-2026 boreddevnl
|
||||
**Copyright (C) 2024-2026 boreddevnl**
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Distributed under the **GNU General Public License v3**. See the `LICENSE` file for details.
|
||||
|
||||
NOTICE
|
||||
------
|
||||
|
||||
This product includes software developed by Chris ("boreddevnl") as part of the BoredOS (Previously Brewkernel/BrewOS) project.
|
||||
|
||||
Copyright (C) 2024–2026 Chris / boreddevnl (previously boreddevhq)
|
||||
|
||||
All source files in this repository contain copyright and license
|
||||
headers that must be preserved in redistributions and derivative works.
|
||||
|
||||
If you distribute or modify this project (in whole or in part),
|
||||
you MUST:
|
||||
|
||||
- Retain all copyright and license headers at the top of each file.
|
||||
- Include this NOTICE file along with any redistributions or
|
||||
derivative works.
|
||||
- Provide clear attribution to the original author in documentation
|
||||
or credits where appropriate.
|
||||
|
||||
The above attribution requirements are informational and intended to
|
||||
ensure proper credit is given. They do not alter or supersede the
|
||||
terms of the GNU General Public License (GPL), which governs this work.
|
||||
> [!IMPORTANT]
|
||||
> This product includes software developed by Chris ("boreddevnl"). You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the `NOTICE` file for more details.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 130" width="100%">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 415 130" width="100%">
|
||||
<style>
|
||||
text {
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,25 +1,43 @@
|
||||
# BoredOS Documentation
|
||||
<div align="center">
|
||||
<h1>BoredOS Documentation</h1>
|
||||
<p><em>Internal guides, architecture, and application development.</em></p>
|
||||
</div>
|
||||
|
||||
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.
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
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
|
||||
|
||||
The documentation is organized into three main categories:
|
||||
|
||||
### 1. [Architecture](architecture/)
|
||||
### 1. 🏗️ [Architecture](architecture/)
|
||||
Explains the logical layout of the kernel and internal components.
|
||||
- [`Core`](architecture/core.md): Kernel source layout and the boot process (Limine, Multiboot2).
|
||||
- [`Memory`](architecture/memory.md): Physical Memory Management (PMM) and Virtual Memory Management (VMM).
|
||||
- [`Filesystem`](architecture/filesystem.md): Virtual File System (VFS) and the RAM-based FAT32 simulation.
|
||||
- [`Window Manager`](architecture/window_manager.md): How the built-in Window Manager natively handles graphics, events, and compositing.
|
||||
|
||||
### 2. [Building and Deployment](build/)
|
||||
### 2. 🔨 [Building and Deployment](build/)
|
||||
Instructions for compiling the OS from source.
|
||||
- [`Toolchain`](build/toolchain.md): Prerequisites and cross-compiler setup (`x86_64-elf-gcc`, `nasm`, `xorriso`).
|
||||
- [`Usage`](build/usage.md): Understanding the Makefile targets, QEMU emulation, and flashing to bare metal hardware.
|
||||
|
||||
### 3. [Application Development](appdev/)
|
||||
### 3. 🚀 [Application Development](appdev/)
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# Creating a Custom App (Step-by-Step)
|
||||
<div align="center">
|
||||
<h1>Creating a Custom App</h1>
|
||||
<p><em>A step-by-step tutorial on writing a new graphical C application.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This guide explains how to write a new "Hello World" application locally, compile it as an `.elf` binary into the `bin/` folder, and launch it inside BoredOS.
|
||||
|
||||
> [!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
|
||||
|
||||
Applications reside entirely in the `src/userland/` directory. Create a new file, for example, `src/userland/gui/hello.c`.
|
||||
@@ -13,30 +21,31 @@ Applications reside entirely in the `src/userland/` directory. Create a new file
|
||||
// src/userland/gui/hello.c
|
||||
#include <stdlib.h>
|
||||
#include <libui.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(void) {
|
||||
// Attempt to open a 300x200 window
|
||||
int wid = ui_create_window("My Custom App", 300, 200, 0);
|
||||
ui_window_t wid = ui_window_create("My Custom App", 100, 100, 300, 200);
|
||||
if (wid < 0) {
|
||||
printf("Error creating window!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write text in center
|
||||
ui_draw_string(wid, "Hello, BoredOS!!", 50, 90, 0xFFFFFFFF);
|
||||
ui_draw_string(wid, 50, 90, "Hello, BoredOS!!", 0xFFFFFFFF);
|
||||
|
||||
// Commit drawing to screen
|
||||
ui_swap_buffers(wid);
|
||||
ui_mark_dirty(wid, 0, 0, 300, 200);
|
||||
|
||||
ui_event_t event;
|
||||
gui_event_t event;
|
||||
while (1) {
|
||||
if (ui_poll_event(&event)) {
|
||||
if (event.window_id == wid && event.type == UI_EVENT_WINDOW_CLOSE) {
|
||||
if (ui_get_event(wid, &event)) {
|
||||
if (event.type == GUI_EVENT_CLOSE) {
|
||||
break; // Exit loop if 'X' is clicked
|
||||
}
|
||||
}
|
||||
|
||||
syscall1(SYSTEM_CMD_YIELD, 0);
|
||||
sys_yield();
|
||||
}
|
||||
|
||||
return 0; // Returning 0 smoothly exits the process via crt0.asm
|
||||
@@ -75,4 +84,7 @@ The main overarching `Makefile` (in the project root) takes binaries from `src/u
|
||||
3. Type `hello` in the terminal and press Enter.
|
||||
4. Your custom window will appear!
|
||||
|
||||
*you can also open your app by opening the file explorer and navigating to the bin directory and double clicking the executable.*
|
||||
> [!NOTE]
|
||||
> You can also open your app by opening the file explorer, navigating to the `bin` directory, and double-clicking the executable.
|
||||
|
||||
---
|
||||
52
docs/appdev/examples/01_hello_cli.md
Normal file
52
docs/appdev/examples/01_hello_cli.md
Normal file
@@ -0,0 +1,52 @@
|
||||
<div align="center">
|
||||
<h1>Example 01: Hello CLI</h1>
|
||||
<p><em>The absolute basics. Writing a terminal program.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This example demonstrates the bare minimum structure of a BoredOS application that outputs text to the standard output (usually the Terminal executing the binary).
|
||||
|
||||
## 📝 Concepts Introduced
|
||||
* Including `stdlib.h` for basic IO.
|
||||
* The `main()` entry point.
|
||||
* Using `printf()` for formatted output.
|
||||
|
||||
---
|
||||
|
||||
## 💻 The Code (`src/userland/cli/hello_world.c`)
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Standard library initialization is handled automatically by crt0.asm
|
||||
|
||||
// Print a simple string to the terminal
|
||||
printf("Hello, World from BoredOS Userland!\n");
|
||||
|
||||
// Print some formatted data
|
||||
int favorite_number = 67;
|
||||
printf("Did you know my favorite number is %d?\n", favorite_number);
|
||||
|
||||
// Returning from main automatically terminates the process cleanly
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ How it Works
|
||||
|
||||
1. **`#include <stdlib.h>`**: We include the SDK's standard library header which gives us access to `printf`.
|
||||
2. **`int main(...)`**: Every process begins execution here (managed transparently by `crt0.asm`).
|
||||
3. **`printf(...)`**: The SDK routes this call internally directly to the `SYS_WRITE` system call, making it available on the terminal.
|
||||
4. **`return 0`**: A successful exit code.
|
||||
|
||||
## 🚀 Running It
|
||||
|
||||
If you build the project, you can open the Terminal and type:
|
||||
```sh
|
||||
/ # hello_world
|
||||
Hello, World from BoredOS Userland!
|
||||
Did you know my favorite number is 67?
|
||||
/ #
|
||||
```
|
||||
71
docs/appdev/examples/02_basic_window.md
Normal file
71
docs/appdev/examples/02_basic_window.md
Normal file
@@ -0,0 +1,71 @@
|
||||
<div align="center">
|
||||
<h1>Example 02: Basic Window</h1>
|
||||
<p><em>An introduction to libui and creating graphical apps.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This example demonstrates how to create an empty window that stays active on the screen until the user explicitly closes it by clicking the 'X' button.
|
||||
|
||||
## 📝 Concepts Introduced
|
||||
* Including `libui.h` and the event structure.
|
||||
* Creating a `ui_window_t` handle.
|
||||
* Creating an infinite event loop using `ui_get_event()`.
|
||||
* Yielding CPU time to the kernel via `sys_yield()`.
|
||||
|
||||
---
|
||||
|
||||
## 💻 The Code (`src/userland/gui/basic_window.c`)
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
#include <libui.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(void) {
|
||||
// 1. Ask the Window Manager to create a new window
|
||||
// Arguments are: Title, X Position, Y Position, Width, Height
|
||||
ui_window_t wid = ui_window_create("My First GUI", 100, 100, 400, 300);
|
||||
|
||||
if (wid < 0) {
|
||||
printf("Failed to create the window!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. Define our event object
|
||||
gui_event_t event;
|
||||
|
||||
// 3. Enter the main event loop
|
||||
while (1) {
|
||||
// ui_get_event is non-blocking. It returns true if an event was waiting.
|
||||
if (ui_get_event(wid, &event)) {
|
||||
|
||||
// Check what type of event occurred
|
||||
if (event.type == GUI_EVENT_CLOSE) {
|
||||
// The user clicked the 'X' button in the titlebar!
|
||||
printf("Window closed cleanly by user.\n");
|
||||
break; // Break the infinite loop
|
||||
}
|
||||
}
|
||||
|
||||
// 4. CRITICAL: Yield the remainder of our timeslice
|
||||
// If we don't do this, the while(1) loop will consume 100% of the CPU
|
||||
// and starve the rest of the OS!
|
||||
sys_yield();
|
||||
}
|
||||
|
||||
// Returning from main will automatically destroy the window and exit the process.
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ How it Works
|
||||
|
||||
1. **Window Handle (`wid`)**: `ui_window_create` sends a request to the kernel. The kernel allocates the memory for the window and returns a numerical ID (the handle) that we use for all future interactions with that specific window.
|
||||
2. **The Event Loop**: Graphical programs run forever until closed. The `while (1)` loop serves this purpose.
|
||||
3. **Polling**: `ui_get_event` asks the kernel, "Hey, did the user click my window or press a key since the last time I asked?". It is non-blocking, so it immediately returns `false` if nothing happened.
|
||||
4. **CPU Yielding**: Since we are constantly polling in a tight loop, we call `sys_yield()` at the end of the loop frame. This politely tells the OS scheduler, "I'm done checking for events, go ahead and let another program run for a bit."
|
||||
|
||||
## 🚀 Running It
|
||||
|
||||
Launch the Terminal and type `basic_window`. You'll see an empty window appear that you can move around the screen!
|
||||
92
docs/appdev/examples/03_bouncing_ball.md
Normal file
92
docs/appdev/examples/03_bouncing_ball.md
Normal file
@@ -0,0 +1,92 @@
|
||||
<div align="center">
|
||||
<h1>Example 03: Bouncing Ball</h1>
|
||||
<p><em>Animating graphics and managing application state.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This example builds upon the `02_basic_window` guide. It demonstrates how to constantly update the screen to simulate a bouncing square moving freely inside the window bounds.
|
||||
|
||||
## 📝 Concepts Introduced
|
||||
* Maintaining application state across frames (Velocity/Position).
|
||||
* Drawing primitives (`ui_fill_rect`, `ui_draw_string`).
|
||||
* The importance of clearing the screen on a new frame.
|
||||
* Explicitly forcing standard visual updates via `ui_mark_dirty()`.
|
||||
|
||||
---
|
||||
|
||||
## 💻 The Code (`src/userland/gui/bounce.c`)
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
#include <libui.h>
|
||||
#include <syscall.h>
|
||||
|
||||
// Window Dimensions
|
||||
#define W_WIDTH 400
|
||||
#define W_HEIGHT 300
|
||||
// Square Dimensions
|
||||
#define SQ_SIZE 30
|
||||
|
||||
int main(void) {
|
||||
ui_window_t wid = ui_window_create("Bouncing Box Animation", 50, 50, W_WIDTH, W_HEIGHT);
|
||||
if (wid < 0) return 1;
|
||||
|
||||
// Define object state variables
|
||||
int pos_x = 50;
|
||||
int pos_y = 50;
|
||||
int vel_x = 2; // Move 2 pixels per frame horizontally
|
||||
int vel_y = 2; // Move 2 pixels per frame vertically
|
||||
|
||||
gui_event_t event;
|
||||
while (1) {
|
||||
// 1. Process Events
|
||||
while (ui_get_event(wid, &event)) {
|
||||
if (event.type == GUI_EVENT_CLOSE) {
|
||||
return 0; // Exit cleanly
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Physics & Logic Update
|
||||
pos_x += vel_x;
|
||||
pos_y += vel_y;
|
||||
|
||||
// Collision logic (Bounce off edges)
|
||||
// The window has a 20px title bar, so the usable client height is W_HEIGHT - 20.
|
||||
if (pos_x <= 0 || pos_x + SQ_SIZE >= W_WIDTH) {
|
||||
vel_x = -vel_x; // Reverse horizontal direction
|
||||
}
|
||||
if (pos_y <= 0 || pos_y + SQ_SIZE >= W_HEIGHT - 20) {
|
||||
vel_y = -vel_y; // Reverse vertical direction
|
||||
}
|
||||
|
||||
// 3. Rendering Update
|
||||
// Step A: Clear the entire background to Black (0xFF000000)
|
||||
ui_draw_rect(wid, 0, 0, W_WIDTH, W_HEIGHT, 0xFF000000);
|
||||
|
||||
// Step B: Draw our shape in Red (0xFFFF0000) at the new position
|
||||
ui_draw_rect(wid, pos_x, pos_y, SQ_SIZE, SQ_SIZE, 0xFFFF0000);
|
||||
|
||||
// Step C: Draw some UI text over the animation in White
|
||||
ui_draw_string(wid, 10, 10, "BoredOS Animation Demo!", 0xFFFFFFFF);
|
||||
|
||||
// Step D: Instruct the compositor to flush our drawing buffer to the physical screen
|
||||
ui_mark_dirty(wid, 0, 0, W_WIDTH, W_HEIGHT);
|
||||
|
||||
// 4. Yield and throttle
|
||||
sys_yield();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ How it Works
|
||||
|
||||
1. **State Management**: We store `pos_x`, `pos_y`, `vel_x`, and `vel_y`. These variables represent the "physics" of our system. Notice that they update *outside* the event-checking logic so that the animation runs even if the user isn't clicking the mouse.
|
||||
2. **Screen Clearing**: We *must* fill the screen with black (`ui_draw_rect(wid, 0, 0, W_WIDTH, W_HEIGHT, ...)`). If we don't clear the screen, the red square will leave a permanent trailing smear everywhere it goes!
|
||||
3. **The Double Buffer**: `ui_draw_rect` and `ui_draw_string` do not immediately appear on your monitor. They just color a hidden buffer within the kernel.
|
||||
4. **`ui_mark_dirty`**: This is the crucial command that tells the kernel Window Manager, "I'm done drawing my frame. Can you quickly copy my hidden buffer over to the real screen now?"
|
||||
|
||||
> [!WARNING]
|
||||
> Because `sys_yield()`'s pause duration depends heavily on CPU load and how many other processes are running (or QEMU emulation speed), tying physics/movement strictly to loops can make the game run faster on faster computers. Advanced developers will want to calculate delta time (time elapsed since the last frame) for smooth motion.
|
||||
92
docs/appdev/examples/04_tcp_client.md
Normal file
92
docs/appdev/examples/04_tcp_client.md
Normal file
@@ -0,0 +1,92 @@
|
||||
<div align="center">
|
||||
<h1>Example 04: TCP HTTP Client</h1>
|
||||
<p><em>Utilizing lwIP to establish an outbound TCP connection.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This advanced example demonstrates the steps required to use the raw network system calls to establish a connection with an external HTTP server and dump the response over the terminal.
|
||||
|
||||
## 📝 Concepts Introduced
|
||||
* Verifying the network state (`sys_network_is_initialized`, `sys_network_has_ip`).
|
||||
* Performing DNS lookups manually via `sys_dns_lookup`.
|
||||
* Managing strict TCP flow logic (`sys_tcp_connect`, send, block for receive).
|
||||
* Using the terminal `SYS_WRITE` output for debugging.
|
||||
|
||||
---
|
||||
|
||||
## 💻 The Code (`src/userland/cli/http_get.c`)
|
||||
|
||||
```c
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syscall.h>
|
||||
|
||||
int main(void) {
|
||||
if (!sys_network_is_initialized() || !sys_network_has_ip()) {
|
||||
printf("Network is unreachable! Make sure you inited the network first!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1. Resolve host name to IP
|
||||
const char *target_host = "boreddev.nl";
|
||||
net_ipv4_address_t server_ip;
|
||||
|
||||
printf("Resolving %s...\n", target_host);
|
||||
if (sys_dns_lookup(target_host, &server_ip) < 0) {
|
||||
printf("DNS Lookup failed.\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Resolved to: %d.%d.%d.%d\n", server_ip.bytes[0], server_ip.bytes[1],
|
||||
server_ip.bytes[2], server_ip.bytes[3]);
|
||||
|
||||
// 2. Establish a TCP connection on port 80 (HTTP)
|
||||
printf("Connecting...\n");
|
||||
if (sys_tcp_connect(&server_ip, 80) < 0) {
|
||||
printf("Connection failed.\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Connected! Sending GET request...\n");
|
||||
|
||||
// 3. Format and send the raw HTTP Request
|
||||
char request[256];
|
||||
strcpy(request, "GET / HTTP/1.1\r\nHost: ");
|
||||
strcat(request, target_host);
|
||||
strcat(request, "\r\nConnection: close\r\n\r\n");
|
||||
|
||||
if (sys_tcp_send(request, strlen(request)) < 0) {
|
||||
printf("Failed to send data.\n");
|
||||
sys_tcp_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 4. Block and wait for response data
|
||||
char recv_buf[512];
|
||||
int bytes_received;
|
||||
|
||||
printf("\n--- RESPONSE ---\n");
|
||||
while ((bytes_received = sys_tcp_recv(recv_buf, sizeof(recv_buf) - 1)) > 0) {
|
||||
recv_buf[bytes_received] = '\0'; // Null-terminate the chunk
|
||||
printf("%s", recv_buf); // Print the chunk to stdout
|
||||
}
|
||||
|
||||
// 5. Cleanup
|
||||
printf("\n--- END RESPONSE ---\n");
|
||||
sys_tcp_close();
|
||||
printf("Connection closed.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ How it Works
|
||||
|
||||
1. **Network Setup**: First, we must ensure the host machine or QEMU environment gave BoredOS a valid IP address via DHCP. The `sys_network_has_ip()` check prevents our app from hanging trying to route data to nowhere.
|
||||
2. **DNS (`sys_dns_lookup`)**: Since we want to connect to a domain name, not a raw IP, we query the DNS server configured by the OS (which it received via DHCP).
|
||||
3. **Connection (`sys_tcp_connect`)**: We block the application thread while the OS performs the 3-way TCP handshake over port 80.
|
||||
4. **Payload (`sys_tcp_send`)**: We format a compliant HTTP/1.1 payload representing a simple GET request for the root directory `/`.
|
||||
5. **Chunked Receiving (`sys_tcp_recv`)**: The server's response might be larger than our `recv_buf` (512 bytes). Therefore, we loop. `sys_tcp_recv` blocks execution until data arrives. If it returns `0`, the remote server cleanly closed the connection (which happens automatically because we specified `Connection: close` in our request payload!).
|
||||
|
||||
## 🚀 Running It
|
||||
|
||||
Make sure QEMU is running with networking enabled. Launch the terminal and type `http_get`. You will see the raw headers and HTML source of the target webpage scroll down the CLI interface!
|
||||
28
docs/appdev/examples/README.md
Normal file
28
docs/appdev/examples/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<div align="center">
|
||||
<h1>Example Applications</h1>
|
||||
<p><em>From basic output to complex Graphical and Network applications.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Welcome to the examples directory! These guides are designed to help you understand how to write C applications for the BoredOS userland, utilizing the custom `libc` SDK.
|
||||
|
||||
The examples are listed in order of increasing complexity. Click on a tutorial to view the complete source code and an explanation of the concepts it introduces.
|
||||
|
||||
## 🟢 Beginner
|
||||
|
||||
* **[`01_hello_cli.md`](01_hello_cli.md)**: The absolute basics. Learn how to write a simple Terminal program that outputs text and processes standard system calls.
|
||||
* **[`02_basic_window.md`](02_basic_window.md)**: An introduction to `libui.h`. Learn how to create an empty window, set up a basic event loop, and handle the "Close" button cleanly.
|
||||
|
||||
## 🟡 Intermediate
|
||||
|
||||
* **[`03_bouncing_ball.md`](03_bouncing_ball.md)**: Dive deeper into graphical rendering. This example introduces the `ui_mark_dirty` command, framerate independence via `sys_yield()`, and state management to animate a shape moving around the screen and bouncing off the window edges.
|
||||
|
||||
## 🔴 Advanced
|
||||
|
||||
* **[`04_tcp_client.md`](04_tcp_client.md)**: Using the lwIP networking stack. This example demonstrates how to perform a DNS lookup, connect to an external server over TCP (like an HTTP server), send a raw request, and print the response to the terminal.
|
||||
|
||||
---
|
||||
|
||||
> [!TIP]
|
||||
> If you want to test these out, simply create a new `.c` file in `src/userland/cli/` (for terminal apps) or `src/userland/gui/` (for windowed apps), paste the example code, then run `make clean && make run` from the project root!
|
||||
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.
|
||||
@@ -1,36 +1,214 @@
|
||||
# Userland SDK Reference
|
||||
<div align="center">
|
||||
<h1>Userland SDK Reference</h1>
|
||||
<p><em>Comprehensive manual for custom libc and system calls in BoredOS.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
## The Custom libc Structure (`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.
|
||||
|
||||
The SDK comprises a few key files containing wrappers around kernel system calls:
|
||||
## Standard Library (`stdlib.h` & `string.h`)
|
||||
|
||||
- `stdlib.h` / `stdlib.c`: Memory allocation (`malloc`, `free`), integer conversion (`itoa`, `atoi`), printing (`printf`, `sprintf`), and random numbers (`rand`, `srand`).
|
||||
- `string.h` / `string.c`: String manipulation utilities (`strlen`, `strcpy`, `strcmp`, `memset`, `memcpy`).
|
||||
- `syscall.h` / `syscall.c`: The raw interface to issue `syscall` assembly instructions, routing requests to the kernel.
|
||||
- `libui.h` / `libui.c`: Graphical interface commands (creating windows, drawing pixels, events).
|
||||
The standard library wrappers provide memory management, string manipulation, and basic IO formatting without needing direct syscalls.
|
||||
|
||||
## System Calls Overview
|
||||
### Memory Allocation
|
||||
* `void* malloc(size_t size);` - Allocate a block of memory on the heap.
|
||||
* `void free(void* ptr);` - Free a previously allocated memory block.
|
||||
* `void* calloc(size_t nmemb, size_t size);` - Allocate and zero-out a block of memory for an array.
|
||||
* `void* realloc(void* ptr, size_t size);` - Resize an existing memory block.
|
||||
|
||||
When a userland application wants to interact with the hardware (print to screen, read a file, create a window), it must ask the kernel via a **System Call**.
|
||||
### Memory Manipulation (`string.h`)
|
||||
* `void* memset(void *s, int c, size_t n);` - Fill a block of memory with a specific byte.
|
||||
* `void* memcpy(void *dest, const void *src, size_t n);` - Copy memory from source to destination.
|
||||
* `void* memmove(void *dest, const void *src, size_t n);` - Safely copy overlapping memory blocks.
|
||||
* `int memcmp(const void *s1, const void *s2, size_t n);` - Compare two memory blocks.
|
||||
|
||||
In BoredOS (`x86_64`), system calls are issued using the `syscall` instruction. The kernel intercepts this instruction and inspects the processor's RAX register to figure out *what* the application wants to do.
|
||||
### String Utilities
|
||||
* `size_t strlen(const char *s);` - Get the length of a string.
|
||||
* `int strcmp(const char *s1, const char *s2);` - Compare two strings lexicographically.
|
||||
* `char* strcpy(char *dest, const char *src);` - Copy a string to a destination buffer.
|
||||
* `char* strcat(char *dest, const char *src);` - Concatenate two strings.
|
||||
|
||||
### Conversion and Formatting
|
||||
* `int atoi(const char *nptr);` - String to integer conversion.
|
||||
* `void itoa(int n, char *buf);` - Integer to string conversion.
|
||||
|
||||
### Input / Output
|
||||
* `void puts(const char *s);` - Print a string followed by a newline to the standard output.
|
||||
* `void printf(const char *fmt, ...);` - Formatted print to standard output (supports `%d`, `%s`, `%x`, etc.).
|
||||
|
||||
### Process Control
|
||||
* `void exit(int status);` - Terminate the current process.
|
||||
* `void sleep(int ms);` - Pause execution for a specified number of milliseconds.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
The custom `libc` provides `syscallX` wrapper functions that abstract the assembly details:
|
||||
```c
|
||||
// Example: Performing a minimal system call from userland
|
||||
int sys_write(int fd, const char *buf, int len) {
|
||||
return syscall3(SYS_WRITE, fd, (uint64_t)buf, len);
|
||||
}
|
||||
#include "math.h"
|
||||
```
|
||||
|
||||
### Notable System Calls
|
||||
### Constants
|
||||
|
||||
- **`SYS_WRITE` (1)**: Currently acts as a generic output mechanism for `printf`, typically routing text to the kernel's serial output for debugging, or to an active text-mode console.
|
||||
- **`SYS_GUI` (3)**: The primary multiplexer for all window manager operations. The arguments define subcommands (like `UI_CREATE_WINDOW`, `UI_FILL_RECT`).
|
||||
- **`SYS_FS` (4)**: Interacts with the virtual filesystem (e.g., `FS_CMD_OPEN`, `FS_CMD_READ`). Under the hood, this reads from the loaded RAMFS or an attached physical ATA disk via the native FAT32 driver.
|
||||
- **`SYS_EXIT` (60)**: Terminates the current process and returns control to the kernel.
|
||||
- **`SYSTEM_CMD_YIELD` (43)**: Instructs the process scheduler to pause the current process and let another process run.
|
||||
| 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 |
|
||||
|
||||
If you are developing a new application, **do not invoke syscalls manually**. Instead, include `stdlib.h` and use the C functions provided.
|
||||
### 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.
|
||||
|
||||
### Process & System Info
|
||||
* `void sys_exit(int status);` - Raw exit syscall.
|
||||
* `int sys_write(int fd, const char *buf, int len);` - Write to a file descriptor (usually fd 1 for stdout).
|
||||
* `void* sys_sbrk(int incr);` - Expand or shrink the process data segment (used internally by `malloc`).
|
||||
* `void sys_kill(int pid);` - Send a kill signal to a process.
|
||||
* `void sys_yield(void);` - Yield the remainder of the process's timeslice to the scheduler.
|
||||
* `int sys_system(int cmd, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);` - Execute a privileged system command (e.g., reboot, shutdown, beep).
|
||||
* `int sys_get_os_info(os_info_t *info);` - Populate an `os_info_t` struct with version and environment details.
|
||||
* `uint64_t sys_get_shell_config(const char *key);` - Retrieve internal shell configuration values.
|
||||
* `void sys_set_text_color(uint32_t color);` - Set the raw CLI text output color.
|
||||
|
||||
### File System API (VFS)
|
||||
Interacting with files and directories using the Virtual File System.
|
||||
* `int sys_open(const char *path, const char *mode);` - Open a file, returning a file descriptor.
|
||||
* `int sys_read(int fd, void *buf, uint32_t len);` - Read from an open file.
|
||||
* `int sys_write_fs(int fd, const void *buf, uint32_t len);` - Write to an open file.
|
||||
* `void sys_close(int fd);` - Close an open file descriptor.
|
||||
* `int sys_seek(int fd, int offset, int whence);` - Reposition the file offset.
|
||||
* `uint32_t sys_tell(int fd);` - Get the current file offset.
|
||||
* `uint32_t sys_size(int fd);` - Get the total file size.
|
||||
* `int sys_delete(const char *path);` - Delete a file.
|
||||
* `int sys_mkdir(const char *path);` - Create a new directory.
|
||||
* `int sys_exists(const char *path);` - Check if a path exists on the filesystem.
|
||||
* `int sys_getcwd(char *buf, int size);` - Get the current working directory string.
|
||||
* `int sys_chdir(const char *path);` - Change the current working directory.
|
||||
* `int sys_list(const char *path, FAT32_FileInfo *entries, int max_entries);` - List directory contents into an array of `FAT32_FileInfo` structs.
|
||||
* `int sys_get_file_info(const char *path, FAT32_FileInfo *info);` - Retrieve metadata for a specific file.
|
||||
|
||||
### Networking Stack API
|
||||
BoredOS includes lwIP for hardware TCP/UDP networking.
|
||||
|
||||
#### Configuration and Status
|
||||
* `int sys_network_init(void);` - Initialize the network stack.
|
||||
* `int sys_network_is_initialized(void);` - Check stack status.
|
||||
* `int sys_network_dhcp_acquire(void);` - Request an IP configuration from a DHCP server.
|
||||
* `int sys_network_has_ip(void);` - Check if the system has a valid IP address.
|
||||
* `int sys_network_get_mac(net_mac_address_t *mac);` - Get the physical MAC address of the NIC.
|
||||
* `int sys_network_get_nic_name(char *name_out);` - Get the name of the active network interface card.
|
||||
* `int sys_network_get_ip(net_ipv4_address_t *ip);` - Get current local IPv4 address.
|
||||
* `int sys_network_set_ip(const net_ipv4_address_t *ip);` - Manually assign a static IP.
|
||||
* `int sys_network_get_gateway(net_ipv4_address_t *ip);` - Get the default gateway IP.
|
||||
* `int sys_network_get_dns(net_ipv4_address_t *ip);` - Get the primary DNS server IP.
|
||||
* `int sys_set_dns_server(const net_ipv4_address_t *ip);` - Set the primary DNS server.
|
||||
* `int sys_get_dns_server(net_ipv4_address_t *ip);` - Retrieve configured DNS server.
|
||||
* `int sys_network_get_stat(int stat_type);` - Get network statistics (packets in/out, drops, etc.).
|
||||
* `void sys_network_force_unlock(void);` - Force release of network stack locks (use with caution).
|
||||
|
||||
#### Communication
|
||||
* `int sys_icmp_ping(const net_ipv4_address_t *dest_ip);` - Send an ICMP echo request.
|
||||
* `int sys_udp_send(const net_ipv4_address_t *dest_ip, uint16_t dest_port, uint16_t src_port, const void *data, size_t data_len);` - Send a raw UDP datagram.
|
||||
* `int sys_dns_lookup(const char *name, net_ipv4_address_t *out_ip);` - Resolve a hostname to an IPv4 address.
|
||||
* `int sys_tcp_connect(const net_ipv4_address_t *ip, uint16_t port);` - Establish a TCP connection to a remote host.
|
||||
* `int sys_tcp_send(const void *data, size_t len);` - Send data over an active TCP socket.
|
||||
* `int sys_tcp_recv(void *buf, size_t max_len);` - Receive data from a TCP socket (blocking).
|
||||
* `int sys_tcp_recv_nb(void *buf, size_t max_len);` - Receive data from a TCP socket (non-blocking).
|
||||
* `int sys_tcp_close(void);` - Close the active TCP socket.
|
||||
|
||||
---
|
||||
|
||||
## Core Data Structures
|
||||
|
||||
### `os_info_t`
|
||||
Contains detailed build and version information about the OS.
|
||||
```c
|
||||
typedef struct {
|
||||
char os_name[64];
|
||||
char os_version[64];
|
||||
char os_codename[64];
|
||||
char kernel_name[64];
|
||||
char kernel_version[64];
|
||||
char build_date[64];
|
||||
char build_time[64];
|
||||
char build_arch[64];
|
||||
} os_info_t;
|
||||
```
|
||||
|
||||
### `FAT32_FileInfo`
|
||||
Represents a filesystem entry.
|
||||
```c
|
||||
typedef struct {
|
||||
char name[256];
|
||||
uint32_t size;
|
||||
uint8_t is_directory;
|
||||
uint32_t start_cluster;
|
||||
uint16_t write_date;
|
||||
uint16_t write_time;
|
||||
} FAT32_FileInfo;
|
||||
```
|
||||
|
||||
### `ProcessInfo`
|
||||
Provides status information for an active process.
|
||||
```c
|
||||
typedef struct {
|
||||
uint32_t pid;
|
||||
char name[64];
|
||||
uint64_t ticks;
|
||||
size_t used_memory;
|
||||
} ProcessInfo;
|
||||
```
|
||||
|
||||
### IP / MAC Addresses
|
||||
Wrappers for raw byte arrays.
|
||||
```c
|
||||
typedef struct { uint8_t bytes[6]; } net_mac_address_t;
|
||||
typedef struct { uint8_t bytes[4]; } net_ipv4_address_t;
|
||||
```
|
||||
|
||||
@@ -1,85 +1,111 @@
|
||||
# UI API (`libui.h`)
|
||||
<div align="center">
|
||||
<h1>UI API (<code>libui.h</code>)</h1>
|
||||
<p><em>Comprehensive manual for interacting with the Window Manager.</em></p>
|
||||
</div>
|
||||
|
||||
For an application to be visible on the screen, it must interact with the BoredOS Window Manager (WM). The tools required for this are located in `src/userland/libc/libui.h` and `libui.c`.
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
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.
|
||||
|
||||
The UI library sends requests (via `SYS_GUI`) to the kernel to reserve an area on the screen (a `Window`) and then issues commands to color specific pixels within that area. The kernel is responsible for compositing this area over other windows.
|
||||
## Window Management
|
||||
|
||||
## Example: Creating a Window
|
||||
A "Window" is a reserved drawing canvas managed by the compositor.
|
||||
|
||||
First, include the library and define an event structure:
|
||||
|
||||
```c
|
||||
#include <libui.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
// 1. Create the window
|
||||
// Arguments: Title, Width, Height, Flags (e.g. 0 for bordered window)
|
||||
int window_id = ui_create_window("Hello World App", 400, 300, 0);
|
||||
|
||||
if (window_id < 0) {
|
||||
printf("Failed to create window!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ... Event loop will go here ...
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
* `ui_window_t ui_window_create(const char *title, int x, int y, int w, int h);`
|
||||
Creates a new window at `(x, y)` with dimensions `w`x`h`. Returns a window handle.
|
||||
**Flags** are currently embedded in the syscall; standard windows include decorations (titlebar, borders).
|
||||
* `void ui_window_set_title(ui_window_t win, const char *title);`
|
||||
Dynamically update the text displayed in the window's titlebar.
|
||||
* `void ui_window_set_resizable(ui_window_t win, bool resizable);`
|
||||
Enable or disable the user's ability to resize the window by dragging its edges.
|
||||
* `void ui_get_screen_size(uint64_t *out_w, uint64_t *out_h);`
|
||||
Query the global screen resolution of the display.
|
||||
|
||||
## Drawing Primitives
|
||||
|
||||
The library offers functions to mutate the window's internal buffer. After issuing drawing commands, you **must** instruct the kernel to push the changes onto the screen.
|
||||
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.**
|
||||
|
||||
* `void ui_draw_rect(ui_window_t win, int x, int y, int w, int h, uint32_t color);`
|
||||
Draw a solid filled rectangle.
|
||||
* `void ui_draw_rounded_rect_filled(ui_window_t win, int x, int y, int w, int h, int radius, uint32_t color);`
|
||||
Fill a rectangle with rounded corners of a specified `radius`.
|
||||
* `void ui_draw_image(ui_window_t win, int x, int y, int w, int h, uint32_t *image_data);`
|
||||
Blit a raw ARGB pixel buffer (`image_data`) directly into the window canvas.
|
||||
* `void ui_mark_dirty(ui_window_t win, int x, int y, int w, int h);`
|
||||
Mark a specific rectangular region of the window as "dirty". The Window Manager will redraw this area on the next compositing pass.
|
||||
|
||||
> [!TIP]
|
||||
> Colors are defined as 32-bit unsigned integers in **ARGB** format: `0xAARRGGBB`.
|
||||
> E.g., `0xFF000000` is opaque black, `0xFFFF0000` is opaque red.
|
||||
|
||||
## Text Rendering
|
||||
|
||||
BoredOS provides multiple text rendering methodologies, including a default system font and scaled/bitmap alternatives.
|
||||
|
||||
* `void ui_draw_string(ui_window_t win, int x, int y, const char *str, uint32_t color);`
|
||||
Draw text using the default system typeface.
|
||||
* `void ui_draw_string_bitmap(ui_window_t win, int x, int y, const char *str, uint32_t color);`
|
||||
Draw text using a secondary fast bitmap font renderer.
|
||||
* `void ui_draw_string_scaled(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale);`
|
||||
Draw text scaled up or down by a floating-point multiplier.
|
||||
* `void ui_draw_string_scaled_sloped(ui_window_t win, int x, int y, const char *str, uint32_t color, float scale, float slope);`
|
||||
Draw scaled text with an italic-like slope/shear applied.
|
||||
* `void ui_set_font(ui_window_t win, const char *path);`
|
||||
Load and set a custom `.ttf` or bitmap font from the filesystem for this window.
|
||||
|
||||
### Font Metrics
|
||||
Used for calculating layout bounds before drawing:
|
||||
* `uint32_t ui_get_string_width(const char *str);`
|
||||
* `uint32_t ui_get_font_height(void);`
|
||||
* `uint32_t ui_get_string_width_scaled(const char *str, float scale);`
|
||||
* `uint32_t ui_get_font_height_scaled(float scale);`
|
||||
|
||||
## Event Handling
|
||||
|
||||
Applications must continuously poll for events inside an infinite `$while(1)` loop.
|
||||
|
||||
* `bool ui_get_event(ui_window_t win, gui_event_t *ev);`
|
||||
Returns `true` if an event was waiting in the queue, populating the `ev` structure. Returns `false` if the queue is empty.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Because `ui_get_event` is non-blocking, you must call `sys_yield();` inside your event loop if no event was received. In BoredOS's **Multi-Core (SMP)** architecture, failing to yield will pin a CPU core to 100% usage, potentially starving other processes.
|
||||
>
|
||||
> All UI syscalls are **Thread-Safe** at the kernel level via the global GUI spinlock.
|
||||
|
||||
### Graphical Event Structure
|
||||
|
||||
```c
|
||||
// Fill the entire window with a solid blue background
|
||||
// Arguments: Window ID, X, Y, Width, Height, ARGB Color value
|
||||
ui_fill_rect(window_id, 0, 0, 400, 300, 0xFF0000FF);
|
||||
|
||||
// Tell the kernel to commit the drawing commands to the screen
|
||||
ui_swap_buffers(window_id);
|
||||
typedef struct {
|
||||
int type; // Specifies the event class (see below)
|
||||
int arg1; // Generic argument 1
|
||||
int arg2; // Generic argument 2
|
||||
int arg3; // Generic argument 3
|
||||
} gui_event_t;
|
||||
```
|
||||
|
||||
Available rendering methods:
|
||||
- `ui_fill_rect(id, x, y, w, h, color)`: Draw a solid rectangle.
|
||||
- `ui_draw_rect(id, x, y, w, h, color)`: Draw an outline of a rectangle.
|
||||
- `ui_draw_line(id, x0, y0, x1, y1, color)`: Bresenham line algorithm.
|
||||
- `ui_draw_string(id, string, x, y, color)`: Render text using the kernel's built-in font.
|
||||
- `ui_update_region(id, x, y, w, h)`: A targeted version of `ui_swap_buffers` that only updates a specific area, saving performance.
|
||||
### Event Types & Arguments
|
||||
|
||||
## Handling the Event Loop
|
||||
| Event Constant | `type` ID | Trigger | `arg1` | `arg2` | `arg3` |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| `GUI_EVENT_NONE` | `0` | Empty event | - | - | - |
|
||||
| `GUI_EVENT_PAINT` | `1` | Window needs redrawing | - | - | - |
|
||||
| `GUI_EVENT_CLICK` | `2` | Mouse click down | X Coord | Y Coord | Button State |
|
||||
| `GUI_EVENT_RIGHT_CLICK` | `3` | Mouse right-click down | X Coord | Y Coord | Button State |
|
||||
| `GUI_EVENT_CLOSE` | `4` | User clicked 'X' button | - | - | - |
|
||||
| `GUI_EVENT_KEY` | `5` | Keyboard key pressed | Keycode | Modifiers | - |
|
||||
| `GUI_EVENT_KEYUP` | `10` | Keyboard key released | Keycode | Modifiers | - |
|
||||
| `GUI_EVENT_MOUSE_DOWN` | `6` | Generic mouse button down | X Coord | Y Coord | Button State |
|
||||
| `GUI_EVENT_MOUSE_UP` | `7` | Generic mouse button release | X Coord | Y Coord | Button State |
|
||||
| `GUI_EVENT_MOUSE_MOVE` | `8` | Mouse cursor moved | X Coord | Y Coord | - |
|
||||
| `GUI_EVENT_MOUSE_WHEEL` | `9` | Scroll wheel rotated | Scroll Delta | - | - |
|
||||
| `GUI_EVENT_RESIZE` | `11` | Window dimensions changed| New Width | New Height | - |
|
||||
|
||||
Graphical applications are event-driven. They stay alive inside a `while (1)` loop, periodically asking the kernel if the user clicked the mouse or pressed a key inside their window.
|
||||
*(Note: Coordinate arguments (`arg1`, `arg2`) for mouse events are typically relative to the top-left corner of the window's client area).*
|
||||
|
||||
```c
|
||||
ui_event_t event;
|
||||
---
|
||||
|
||||
// Main UI Loop
|
||||
while (1) {
|
||||
// ui_poll_event is non-blocking. It returns 1 if an event occurred, 0 otherwise.
|
||||
if (ui_poll_event(&event)) {
|
||||
> [!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.
|
||||
|
||||
// The WM dispatch sets event.window_id
|
||||
// We only care about events meant for our specific window
|
||||
if (event.window_id == window_id) {
|
||||
|
||||
if (event.type == UI_EVENT_MOUSE_DOWN) {
|
||||
printf("User clicked at X:%d Y:%d\n", event.mouse_x, event.mouse_y);
|
||||
|
||||
// Respond visually to the click
|
||||
ui_fill_rect(window_id, event.mouse_x, event.mouse_y, 10, 10, 0xFFFF0000); // Red dot
|
||||
ui_swap_buffers(window_id);
|
||||
}
|
||||
else if (event.type == UI_EVENT_WINDOW_CLOSE) {
|
||||
// Start tearing down the application safely
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent 100% CPU usage by yielding execution time back to the OS scheduler
|
||||
syscall1(SYSTEM_CMD_YIELD, 0);
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
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...
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,4 +1,9 @@
|
||||
# Core Architecture
|
||||
<div align="center">
|
||||
<h1>Core Architecture</h1>
|
||||
<p><em>Overview of BoredOS kernel layout, boot process, and userspace transition.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
BoredOS is a 64-bit hobbyist operating system designed for the x86_64 architecture. While it features kernel-space drivers and a built-in window manager, it supports fully-isolated userspace applications and includes a networking stack.
|
||||
|
||||
@@ -14,7 +19,7 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
||||
- **`fs/`**: Filesystem implementations. The system uses a Virtual File System (VFS) abstraction alongside an in-memory FAT32 filesystem with support for drives over ATA that are formatted as FAT32 (plain/MBR).
|
||||
- **`mem/`**: Physical and virtual memory management. It controls page frame allocation, paging, and kernel heap operations.
|
||||
- **`net/`**: The networking stack. BoredOS relies on `lwIP` for processing IPv4 and TCP/UDP traffic, interacting with a range of NICs via `net/nic/`.
|
||||
- **`sys/`**: System calls and process management. The ELF loader resides here, parsing userland binaries and setting them up for execution.
|
||||
- **`sys/`**: System calls and process management. The ELF loader resides here, alongside the Symmetric Multi-Processing (**smp.c**) bringup and Local APIC (**lapic.c**) management logic.
|
||||
- **`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/`).
|
||||
|
||||
@@ -23,17 +28,31 @@ The OS heavily relies on module separation. The `src/` directory is logically sp
|
||||
BoredOS uses **Limine** as its primary bootloader.
|
||||
|
||||
1. **Limine Initialization**: The machine firmware (BIOS or UEFI) loads Limine. Limine parses `limine.conf`, sets up an early graphical framebuffer, and reads the kernel ELF file into memory.
|
||||
2. **Multiboot2 Protocol**: The kernel expects the Limine boot protocol (which is compatible with modern Multiboot specifications). Passing a framebuffer and memory map is handled natively by Limine's request structures (defined locally via `limine.h`).
|
||||
3. **Kernel Entry (`main.c`)**: The entry point `_start` is called. It immediately initializes the serial port for debugging, sets up core structures (GDT/IDT), initializes the physical memory manager based on the Limine memory map, and starts the virtual memory manager.
|
||||
4. **Driver Initialization**: PCI buses are scanned, finding the network card or disk controllers. The filesystem is mounted.
|
||||
5. **Window Manager**: The UI is drawn on top of the Limine-provided framebuffer.
|
||||
2. **Multiboot2 & SMP Protocol**: The kernel expects the Limine boot protocol. It makes a specific **SMP Request** to Limine to locate and bring up all available CPU cores.
|
||||
3. **Kernel Entry (`main.c`)**: The entry point `_start` is called on the Bootstrap Processor (BSP). It initializes the serial port, GDT/IDT, memory management, and paging.
|
||||
4. **AP Bringup**: The BSP calls `smp_init()`, which sends the Startup Inter-Processor Interrupt (SIPI) sequence to all Application Processors (APs). Each AP initializes its own local GDT, TSS, and Page Tables before entering an idle loop.
|
||||
5. **Driver Initialization**: PCI buses are scanned, finding the network card or disk controllers. The filesystem is mounted.
|
||||
6. **Window Manager**: The UI is drawn on top of the Limine-provided framebuffer.
|
||||
|
||||
## 🧵 Multi-Core & Scheduling
|
||||
|
||||
BoredOS utilizes Symmetric Multi-Processing (SMP) to distribute workloads across all available CPU cores.
|
||||
|
||||
- **LAPIC & IPIs**: Each CPU has its own Local APIC. The kernel uses Inter-Processor Interrupts (IPIs) for inter-core communication, specifically for triggering the scheduler on other cores (`vector 0x41`).
|
||||
- **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
|
||||
|
||||
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application (like `browser.elf` or `viewer.elf`) is launched, the kernel:
|
||||
1. Loads the ELF file from the filesystem using the ELF parser in `sys/elf.c`.
|
||||
2. Allocates a new virtual address space (Page Directory) for the process.
|
||||
3. Maps the executable segments according to the ELF headers.
|
||||
4. Switches to User Mode (Ring 3) via the `iretq` instruction, jumping into the application's entry point (`crt0.asm`).
|
||||
The OS supports privilege separation (Ring 0 vs. Ring 3). When an application is launched, the kernel:
|
||||
|
||||
Programs then interact with the core kernel using system calls (`syscall.c`).
|
||||
1. Loads the ELF file from the filesystem.
|
||||
2. Assigns the process to a CPU core via a round-robin distribution strategy.
|
||||
3. Allocates a new virtual address space (Page Directory) for the process.
|
||||
4. Maps the executable segments according to the ELF headers.
|
||||
5. Switches to User Mode (Ring 3) via the `iretq` instruction.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Programs interact with the core kernel using system calls (`syscall.c`). Multitasking is achieved by pre-empting user processes on their respective cores.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Filesystem Architecture
|
||||
<div align="center">
|
||||
<h1>Filesystem Architecture</h1>
|
||||
<p><em>Virtual File System layer and FAT32 abstraction in BoredOS.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
BoredOS implements a rudimentary but functional filesystem layer designed to support reading system assets and user applications during runtime.
|
||||
|
||||
@@ -10,19 +15,23 @@ Key VFS functionalities include:
|
||||
- **File Descriptors**: Mapping integer IDs to internal file structures for userland processes.
|
||||
- **Standard Operations**: Standardizing `open()`, `read()`, `write()`, `close()`, `seek()`, and directory listings.
|
||||
- **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
|
||||
|
||||
The primary filesystem logic in `fat32.c` has a dual nature, supporting both an in-memory RAM filesystem for booting and standard block devices for external storage.
|
||||
The primary filesystem logic in `fat32.c` handles both in-memory RAM-based filesystem simulation and physical ATA block devices.
|
||||
|
||||
### Booting and the RAMFS
|
||||
Since BoredOS boots from a CD-ROM ISO image generated by `xorriso`, it does not read directly off the CD to execute applications.
|
||||
1. **ISO Booting**: During boot, Limine loads necessary files (such as userland `.elf` binaries, fonts, and wallpapers) into memory as standard boot modules.
|
||||
2. **RAM Simulation**: The FAT32 filesystem code parses these loaded memory modules and automatically constructs a synthetic FAT32 directory tree inside RAM.
|
||||
3. **Root Filesystem**: All active execution of built-in GUI and CLI apps occurs off this read-only, in-memory FAT32 simulation.
|
||||
### Storage Support
|
||||
|
||||
### ATA Disk Support
|
||||
Beyond the core RAMFS used for booting, the FAT32 implementation natively supports interacting with permanent storage:
|
||||
1. **ATA Block Driver**: The kernel features an ATA block device driver capable of communicating with physical hard disks (or raw disk images attached via QEMU).
|
||||
2. **Partition Compatibility**: The driver can recognize and natively mount external ATA disks formatted as single FAT32 filesystems or structured with a Master Boot Record (MBR) partition table.
|
||||
3. **VFS Integration**: When external storage is mounted, the VFS delegates operations down directly to the FAT32 driver, which will read native sectors across the ATA interface.
|
||||
BoredOS supports two main types of storage for its FAT32 implementation:
|
||||
|
||||
1. **RAMFS (Boot Modules)**: During boot, Limine loads necessary files (such as userland `.elf` binaries, fonts, and wallpapers) into memory as standard boot modules. The FAT32 code parses these loaded memory modules and automatically constructs a synthetic FAT32 directory tree inside RAM (mounted as `A:`).
|
||||
2. **ATA Drives**: The kernel includes a basic PIO-based ATA driver that can detect and read/write to physical IDE/PATA hard disks.
|
||||
- **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
|
||||
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.
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Memory Management
|
||||
<div align="center">
|
||||
<h1>Memory Management</h1>
|
||||
<p><em>Physical and Virtual Memory coordination in x86_64 Long Mode.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@@ -9,15 +14,27 @@ The PMM is responsible for tracking which physical RAM frames (usually 4KB each)
|
||||
1. **Memory Map**: During boot, Limine provides a memory map detailing the available, reserved, and unusable physical memory regions.
|
||||
2. **Bitmap Allocator**: The core PMM uses a bitmap-based allocation strategy. Each bit in the bitmap represents a single physical page (frame). If a bit is `1`, the page is in use; if `0`, it is free.
|
||||
3. **Allocation**: When a new page is requested (e.g., for userland space or kernel heap), the PMM scans the bitmap for the first available zero bit, marks it as used, and returns the physical address.
|
||||
4. **SMP Safety**: In a multi-core environment, the PMM and VMM are protected by **Spinlocks** to prevent two CPUs from allocating the same frame or modifying page tables simultaneously.
|
||||
|
||||
> [!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
|
||||
|
||||
BoredOS uses 4-level paging (PML4), a requirement for x86_64 long mode, dividing the virtual address space between the kernel and userland.
|
||||
|
||||
- **Kernel Space**: The kernel relies on a higher-half design where its code, data, and heap are mapped to high addresses (typically above `0xFFFF800000000000`). This ensures the kernel remains mapped and accessible regardless of which user process is currently active.
|
||||
- **User Space**: Userland applications are loaded into lower virtual addresses (starting frequently around `0x40000000`).
|
||||
- **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 (e.g., for stack growth or lazy loading) or terminate the process for a segmentation fault.
|
||||
- **Kernel Space**: The kernel relies on a higher-half design where its code, data, and heap are mapped to high addresses (typically above `0xFFFF800000000000`).
|
||||
- **Per-CPU Structures**: Each CPU core maintains its own architectural state in memory:
|
||||
* **Per-CPU GDT**: Each core is initialized with its own Global Descriptor Table.
|
||||
* **Per-CPU TSS**: Each core has a dedicated Task State Segment containing the `RSP0` pointer for its own kernel stack, ensuring safe interrupt handling across cores.
|
||||
- **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
|
||||
|
||||
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 using the VMM.
|
||||
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.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The kernel heap is a shared resource; therefore, all `kmalloc` and `kfree` operations are guarded by a global spinlock to ensure thread safety during multi-core execution.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Window Manager (WM)
|
||||
<div align="center">
|
||||
<h1>Window Manager (WM)</h1>
|
||||
<p><em>The native graphical subsystem compositing and event routing.</em></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@@ -8,7 +13,10 @@ BoredOS features a fully custom, graphical Window Manager built directly into th
|
||||
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.
|
||||
3. **Compositing**: Once per frame or upon request, the entire back buffer (or dirty regions) is copied to the actual Limine physical framebuffer memory, making the changes visible instantly.
|
||||
|
||||
## Window System (`wm.c`)
|
||||
> [!TIP]
|
||||
> The performance of the window manager heavily depends on minimizing the "dirty regions" drawn in the compositing loop rather than sweeping the whole screen.
|
||||
|
||||
## 🪟 Window System (`wm.c`)
|
||||
|
||||
The windowing system is built around a linked list of `Window` structures.
|
||||
|
||||
@@ -24,10 +32,17 @@ The WM acts as the central hub for input routing.
|
||||
2. **Hit Testing**: The WM checks these coordinates against the bounding boxes of existing windows. It handles dragging logic (if the user clicks a title bar) or focus changes.
|
||||
3. **Event Queue**: If a userland application owns the window that was clicked, the WM packages the input (coordinates, button state) into an event message and drops it into the owning process's event queue. The application can retrieve these via the custom libc UI functions.
|
||||
|
||||
## Userland API (`libui.c`)
|
||||
|
||||
Applications do not talk to the hardware directly. Instead, they use a library (`libui.c`) which makes specialized system calls (`SYS_GUI`).
|
||||
|
||||
- **Window Creation**: `ui_create_window()` asks the kernel to instantiate a new window object and returns a handle.
|
||||
- **Drawing**: Applications can request the kernel to fill rectangles or plot pixels inside their designated window area.
|
||||
- **Event Polling**: The UI loop inside an app continuously calls `ui_poll_event()` to respond to mouse clicks and window movement dispatched by the kernel WM.
|
||||
|
||||
## 🧵 Multi-Core Safety & Performance
|
||||
|
||||
With the introduction of Symmetric Multi-Processing (SMP), the Window Manager (WM) was redesigned to ensure stability and high performance across multiple cores.
|
||||
|
||||
1. **Granular Window Locks**: Each `Window` object possesses its own `spinlock_t lock;`. User applications concurrently draw directly into their own window buffers without stalling the rest of the system. The global `wm_lock` is reserved strictly for altering global structures like window z-order or syncing buffers to the screen compositing layer.
|
||||
2. **Per-CPU Rendering State**: To facilitate simultaneous GUI system calls across all CPU cores, the low-level rendering context (`g_render_target` array) is isolated per-CPU using the core ID. This allows completely lockless multi-core pixel rasterization, drastically reducing rendering bottlenecks.
|
||||
3. **Deferred Compositing**: Final screen composition (`wm_paint`) is scheduled to the main kernel idle loop on the Bootstrap Processor (BSP). This enables application cores to continue processing logic seamlessly while the GUI asynchronously handles flipping the physical framebuffer.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Because application rendering (rasterizing geometry into a window's backbuffer) is SMP-safe and lock-free across cores, GUI performance scales linearly with the number of CPUs active.
|
||||
|
||||
---
|
||||
|
||||
13
docs/build/usage.md
vendored
13
docs/build/usage.md
vendored
@@ -33,14 +33,21 @@ To run BoredOS successfully (either in emulation or on bare metal), your target
|
||||
|
||||
To test the generated ISO quickly without real hardware, use the QEMU emulator:
|
||||
|
||||
For MacOS:
|
||||
```sh
|
||||
make run
|
||||
make run-mac
|
||||
```
|
||||
For Linux:
|
||||
```sh
|
||||
make run-linux
|
||||
```
|
||||
For Windows:
|
||||
```sh
|
||||
make run-windows
|
||||
```
|
||||
|
||||
This command invokes QEMU with specific arguments:
|
||||
- `-m 4G`: Allocates 4 Gigabytes of RAM.
|
||||
- `-cdrom boredos.iso`: Mounts the built OS image as a CD-ROM.
|
||||
- `-netdev user...`: Sets up a basic NAT network interface for the OS's networking stack.
|
||||
- `-smp 4`: Enables 4 CPU cores.
|
||||
- `-drive file=disk.img...`: Attaches a raw disk image included in this release of BoredOS.
|
||||
|
||||
|
||||
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` or `.bashrc` 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
|
||||
|
||||
BIN
screenshot.jpg
BIN
screenshot.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 317 KiB |
BIN
screenshot.png
Executable file
BIN
screenshot.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 988 KiB |
@@ -7,9 +7,13 @@ global isr1_wrapper
|
||||
global isr8_wrapper
|
||||
global isr12_wrapper
|
||||
global isr14_wrapper
|
||||
global isr128_wrapper
|
||||
global isr_sched_ipi_wrapper
|
||||
extern timer_handler
|
||||
extern keyboard_handler
|
||||
extern mouse_handler
|
||||
extern sched_ipi_handler
|
||||
extern syscall_handler_c
|
||||
extern exception_handler_c
|
||||
|
||||
; Helper to send EOI (End of Interrupt) to PIC
|
||||
@@ -41,7 +45,11 @@ isr%2_wrapper:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state
|
||||
test qword [rsp + 144], 3
|
||||
jz %%skip_swap
|
||||
swapgs
|
||||
%%skip_swap:
|
||||
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
@@ -72,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
|
||||
@@ -85,6 +99,12 @@ isr1_wrapper:
|
||||
isr12_wrapper:
|
||||
ISR_NOERRCODE mouse_handler, 44
|
||||
|
||||
isr_sched_ipi_wrapper:
|
||||
ISR_NOERRCODE sched_ipi_handler, 65
|
||||
|
||||
isr128_wrapper:
|
||||
ISR_NOERRCODE syscall_handler_c, 128
|
||||
|
||||
; Common exception macro for exceptions WITHOUT error code
|
||||
%macro EXCEPTION_NOERRCODE 1
|
||||
global exc%1_wrapper
|
||||
@@ -154,7 +174,11 @@ exception_common:
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save SSE/FPU state
|
||||
test qword [rsp + 144], 3
|
||||
jz .skip_swap_exc
|
||||
swapgs
|
||||
.skip_swap_exc:
|
||||
|
||||
sub rsp, 512
|
||||
fxsave [rsp]
|
||||
|
||||
@@ -186,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
|
||||
|
||||
2405
src/core/cmd.c
2405
src/core/cmd.c
File diff suppressed because it is too large
Load Diff
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;
|
||||
@@ -113,14 +123,49 @@ void k_shutdown(void) {
|
||||
outw(0x4004, 0x3400); // VirtualBox fallback
|
||||
}
|
||||
|
||||
volatile uint64_t beep_end_tick = 0;
|
||||
bool beep_active = false;
|
||||
|
||||
void k_beep(int freq, int ms) {
|
||||
if (freq <= 0) return;
|
||||
if (freq <= 0) {
|
||||
outb(0x61, inb(0x61) & 0xFC);
|
||||
beep_active = false;
|
||||
return;
|
||||
}
|
||||
int div = 1193180 / freq;
|
||||
outb(0x43, 0xB6);
|
||||
outb(0x42, div & 0xFF);
|
||||
outb(0x42, (div >> 8) & 0xFF);
|
||||
outb(0x61, inb(0x61) | 0x03);
|
||||
k_sleep(ms);
|
||||
outb(0x61, inb(0x61) & 0xFC);
|
||||
|
||||
uint32_t ticks = ms / 16;
|
||||
if (ticks == 0 && ms > 0) ticks = 1;
|
||||
extern volatile uint64_t kernel_ticks;
|
||||
beep_end_tick = kernel_ticks + ticks;
|
||||
beep_active = true;
|
||||
}
|
||||
|
||||
void k_beep_process(void) {
|
||||
if (beep_active) {
|
||||
extern volatile uint64_t kernel_ticks;
|
||||
if (kernel_ticks >= beep_end_tick) {
|
||||
outb(0x61, inb(0x61) & 0xFC);
|
||||
beep_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Kernel string utilities
|
||||
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);
|
||||
@@ -23,5 +25,7 @@ void k_sleep(int ms);
|
||||
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
|
||||
|
||||
244
src/core/main.c
244
src/core/main.c
@@ -16,9 +16,24 @@
|
||||
#include "wm.h"
|
||||
#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")))
|
||||
@@ -42,11 +57,33 @@ static volatile struct limine_module_request module_request = {
|
||||
.revision = 0
|
||||
};
|
||||
|
||||
__attribute__((used, section(".requests")))
|
||||
static volatile struct limine_smp_request smp_request = {
|
||||
.id = LIMINE_SMP_REQUEST,
|
||||
.revision = 0,
|
||||
.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
|
||||
};
|
||||
|
||||
@@ -72,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) {
|
||||
@@ -121,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");
|
||||
@@ -184,14 +277,73 @@ void kmain(void) {
|
||||
fat32_mkdir("/Library/images/gif");
|
||||
fat32_mkdir("/Library/Fonts");
|
||||
fat32_mkdir("/Library/DOOM");
|
||||
fat32_mkdir("/Library/bsh");
|
||||
fat32_mkdir("/docs");
|
||||
fat32_mkdir("/root");
|
||||
fat32_mkdir("/root/Desktop");
|
||||
fat32_mkdir("/root/Pictures");
|
||||
fat32_mkdir("/root/Documents");
|
||||
fat32_mkdir("/root/Downloads");
|
||||
|
||||
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];
|
||||
|
||||
@@ -199,6 +351,15 @@ void kmain(void) {
|
||||
if (fs_starts_with(clean_path, "boot():")) clean_path += 7;
|
||||
else if (fs_starts_with(clean_path, "boot:///")) clean_path += 8;
|
||||
|
||||
int len = 0;
|
||||
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("[INIT] Parsing TAR initrd: ");
|
||||
serial_write(clean_path);
|
||||
serial_write("\n");
|
||||
tar_parse(mod->address, mod->size);
|
||||
} else {
|
||||
char dir_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; clean_path[j]; j++) {
|
||||
@@ -216,12 +377,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");
|
||||
|
||||
@@ -231,10 +393,22 @@ void kmain(void) {
|
||||
ps2_init();
|
||||
asm("sti");
|
||||
|
||||
lapic_init();
|
||||
|
||||
if (smp_request.response != NULL) {
|
||||
uint32_t online = smp_init(smp_request.response);
|
||||
log_ok("SMP initialized");
|
||||
} else {
|
||||
serial_write("[INIT] No SMP response from bootloader\n");
|
||||
smp_init(NULL);
|
||||
}
|
||||
|
||||
wm_init();
|
||||
|
||||
asm volatile("sti");
|
||||
|
||||
extern void bootfs_refresh_from_disk(void);
|
||||
bootfs_refresh_from_disk();
|
||||
|
||||
while (1) {
|
||||
wm_process_input();
|
||||
|
||||
@@ -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
|
||||
|
||||
33
src/core/version.c
Normal file
33
src/core/version.c
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 "syscall.h"
|
||||
#include <stddef.h>
|
||||
|
||||
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
||||
|
||||
void get_os_info(os_info_t *info) {
|
||||
if (!info) return;
|
||||
|
||||
char *p = (char *)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.2.1";
|
||||
const char *os_codename = "Voyager";
|
||||
const char *kernel_name = "Boredkernel";
|
||||
const char *kernel_version = "4.1.0-stable";
|
||||
const char *build_date = __DATE__;
|
||||
const char *build_time = __TIME__;
|
||||
const char *build_arch = "x86_64";
|
||||
|
||||
int j;
|
||||
j = 0; while (os_name[j] && j < 63) { info->os_name[j] = os_name[j]; j++; } info->os_name[j] = '\0';
|
||||
j = 0; while (os_version[j] && j < 63) { info->os_version[j] = os_version[j]; j++; } info->os_version[j] = '\0';
|
||||
j = 0; while (os_codename[j] && j < 63) { info->os_codename[j] = os_codename[j]; j++; } info->os_codename[j] = '\0';
|
||||
j = 0; while (kernel_name[j] && j < 63) { info->kernel_name[j] = kernel_name[j]; j++; } info->kernel_name[j] = '\0';
|
||||
j = 0; while (kernel_version[j] && j < 63) { info->kernel_version[j] = kernel_version[j]; j++; } info->kernel_version[j] = '\0';
|
||||
j = 0; while (build_date[j] && j < 63) { info->build_date[j] = build_date[j]; j++; } info->build_date[j] = '\0';
|
||||
j = 0; while (build_time[j] && j < 63) { info->build_time[j] = build_time[j]; j++; } info->build_time[j] = '\0';
|
||||
j = 0; while (build_arch[j] && j < 63) { info->build_arch[j] = build_arch[j]; j++; } info->build_arch[j] = '\0';
|
||||
}
|
||||
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;
|
||||
kfree(pbuf);
|
||||
kfree(buffer);
|
||||
return true;
|
||||
if (pbuf) {
|
||||
if (disk->read_sector(disk, start, pbuf) == 0) {
|
||||
fat32 = is_fat32_bpb(pbuf);
|
||||
}
|
||||
kfree(pbuf);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: check if sector 0 itself is a raw FAT32 BPB (no partition table)
|
||||
if (is_fat32_bpb(buffer)) {
|
||||
disk_register_partition(disk, partitions[i].lba_start,
|
||||
partitions[i].sector_count, fat32, part_num);
|
||||
part_num++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Parse MBR to find partitions
|
||||
parse_mbr_partitions(new_disk);
|
||||
}
|
||||
}
|
||||
|
||||
// === 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) {
|
||||
// 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");
|
||||
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
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "io.h"
|
||||
#include "wm.h"
|
||||
#include "network.h"
|
||||
#include "lapic.h"
|
||||
#include "smp.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
extern void serial_print(const char *s);
|
||||
@@ -14,20 +16,49 @@ extern void serial_print_hex(uint64_t n);
|
||||
volatile uint64_t kernel_ticks = 0;
|
||||
|
||||
uint64_t timer_handler(registers_t *regs) {
|
||||
if (smp_this_cpu_id() == 0) {
|
||||
kernel_ticks++;
|
||||
wm_timer_tick();
|
||||
network_process_frames();
|
||||
|
||||
outb(0x20, 0x20); // EOI after processing to prevent nested timer interrupts
|
||||
extern void k_beep_process(void);
|
||||
k_beep_process();
|
||||
}
|
||||
|
||||
outb(0x20, 0x20);
|
||||
extern uint64_t process_schedule(uint64_t current_rsp);
|
||||
return process_schedule((uint64_t)regs);
|
||||
uint64_t new_rsp = process_schedule((uint64_t)regs);
|
||||
|
||||
if (smp_cpu_count() > 1) {
|
||||
lapic_send_ipi_all();
|
||||
}
|
||||
|
||||
return new_rsp;
|
||||
}
|
||||
|
||||
// --- 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',
|
||||
@@ -55,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) {
|
||||
@@ -90,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);
|
||||
}
|
||||
}
|
||||
@@ -103,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);
|
||||
}
|
||||
}
|
||||
@@ -165,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();
|
||||
@@ -227,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
|
||||
|
||||
BIN
src/fonts/Emoji/NotoEmoji-VariableFont_wght.ttf
Normal file
BIN
src/fonts/Emoji/NotoEmoji-VariableFont_wght.ttf
Normal file
Binary file not shown.
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
|
||||
1668
src/fs/fat32.c
1668
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
|
||||
128
src/fs/tar.c
Normal file
128
src/fs/tar.c
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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 "tar.h"
|
||||
#include "fat32.h"
|
||||
|
||||
// The standard TAR header block is 512 bytes.
|
||||
struct tar_header {
|
||||
char filename[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char pad[12];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Helper: parse tar octal field representation
|
||||
static uint64_t tar_parse_octal(const char *str, int size) {
|
||||
uint64_t result = 0;
|
||||
while (size-- > 0) {
|
||||
if (*str >= '0' && *str <= '7') {
|
||||
result = (result << 3) + (*str - '0');
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper: Make directories sequentially for nested paths
|
||||
static void tar_mkdir_recursive(const char *path) {
|
||||
char temp[256];
|
||||
int i = 0;
|
||||
if (path[0] == '/') {
|
||||
temp[0] = '/';
|
||||
i = 1;
|
||||
}
|
||||
while (path[i] && i < 255) {
|
||||
temp[i] = path[i];
|
||||
if (path[i] == '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
temp[i] = '/';
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i > 0 && temp[i - 1] != '/') {
|
||||
temp[i] = '\0';
|
||||
fat32_mkdir(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void tar_parse(void *archive, uint64_t archive_size) {
|
||||
uint8_t *ptr = (uint8_t *)archive;
|
||||
uint8_t *end = ptr + archive_size;
|
||||
|
||||
while (ptr + 512 <= end) {
|
||||
struct tar_header *header = (struct tar_header *)ptr;
|
||||
|
||||
// End of archive is marked by empty blocks
|
||||
if (header->filename[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t file_size = tar_parse_octal(header->size, 11);
|
||||
|
||||
char full_path[256];
|
||||
// Ensure path starts with a '/' for VFS consistency
|
||||
if (header->filename[0] != '/') {
|
||||
full_path[0] = '/';
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 254) {
|
||||
full_path[j + 1] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j + 1] = '\0';
|
||||
} else {
|
||||
int j = 0;
|
||||
while (header->filename[j] && j < 255) {
|
||||
full_path[j] = header->filename[j];
|
||||
j++;
|
||||
}
|
||||
full_path[j] = '\0';
|
||||
}
|
||||
|
||||
if (header->typeflag == '5') {
|
||||
// It's a directory
|
||||
tar_mkdir_recursive(full_path);
|
||||
} else if (header->typeflag == '0' || header->typeflag == '\0') {
|
||||
// It's a normal file
|
||||
// First ensure the parent directory exists
|
||||
char parent_path[256];
|
||||
int last_slash = -1;
|
||||
for (int j = 0; full_path[j]; j++) {
|
||||
parent_path[j] = full_path[j];
|
||||
if (full_path[j] == '/') {
|
||||
last_slash = j;
|
||||
}
|
||||
}
|
||||
if (last_slash > 0) {
|
||||
parent_path[last_slash] = '\0';
|
||||
tar_mkdir_recursive(parent_path);
|
||||
}
|
||||
|
||||
// Extract the file data block directly into the VFS
|
||||
FAT32_FileHandle *fh = fat32_open(full_path, "w");
|
||||
if (fh && fh->valid) {
|
||||
fat32_write(fh, ptr + 512, file_size);
|
||||
fat32_close(fh);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance pointer to the next file header
|
||||
// Header block (512) + File data (padded to 512-byte multiples)
|
||||
uint64_t data_blocks = (file_size + 511) / 512;
|
||||
ptr += 512 + (data_blocks * 512);
|
||||
}
|
||||
}
|
||||
13
src/fs/tar.h
Normal file
13
src/fs/tar.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 TAR_H
|
||||
#define TAR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Parse a TAR archive located in memory and extract its contents into the current filesystem (fatal32 RAM disk).
|
||||
void tar_parse(void *archive, uint64_t archive_size);
|
||||
|
||||
#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
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <stdint.h>
|
||||
#include "limine.h"
|
||||
#include "platform.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
// --- Internal State ---
|
||||
// memory_pool is no longer a single pointer, as we now manage multiple regions.
|
||||
@@ -16,6 +17,7 @@ static size_t total_allocated = 0;
|
||||
static size_t peak_allocated = 0;
|
||||
static uint32_t allocation_counter = 0;
|
||||
static bool initialized = false;
|
||||
static spinlock_t mm_lock = SPINLOCK_INIT;
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
extern void serial_write_num(uint32_t n);
|
||||
@@ -174,8 +176,7 @@ static void remove_block_at(int idx) {
|
||||
void* kmalloc_aligned(size_t size, size_t alignment) {
|
||||
if (!initialized || size == 0) return NULL;
|
||||
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&mm_lock);
|
||||
|
||||
if (alignment == 0) alignment = 8;
|
||||
size = (size + 7) & ~7ULL; // Ensure size is multiple of 8
|
||||
@@ -245,12 +246,12 @@ void* kmalloc_aligned(size_t size, size_t alignment) {
|
||||
if (total_allocated > peak_allocated) peak_allocated = total_allocated;
|
||||
|
||||
mem_memset(ptr, 0, size);
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -261,8 +262,7 @@ void* kmalloc(size_t size) {
|
||||
void kfree(void *ptr) {
|
||||
if (ptr == NULL || !initialized) return;
|
||||
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&mm_lock);
|
||||
|
||||
int block_idx = -1;
|
||||
for (int i = 0; i < block_count; i++) {
|
||||
@@ -273,7 +273,7 @@ void kfree(void *ptr) {
|
||||
}
|
||||
|
||||
if (block_idx == -1) {
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ void kfree(void *ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&mm_lock, rflags);
|
||||
}
|
||||
|
||||
void* krealloc(void *ptr, size_t new_size) {
|
||||
@@ -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)
|
||||
@@ -224,7 +224,7 @@ int network_tcp_connect(const ipv4_address_t *ip, uint16_t port) {
|
||||
|
||||
uint32_t start = sys_now();
|
||||
asm volatile("sti");
|
||||
while (sys_now() - start < 5000) { // 5 second timeout
|
||||
while (sys_now() - start < 15000) { // 15 second timeout
|
||||
network_poll_internal();
|
||||
if (tcp_connect_done) { asm volatile("cli"); network_processing = 0; return 0; }
|
||||
if (tcp_connect_error) { asm volatile("cli"); network_processing = 0; return -1; }
|
||||
@@ -253,7 +253,7 @@ int network_tcp_recv(void *buf, size_t max_len) {
|
||||
if (tcp_closed) { network_processing = 0; return 0; } // End of stream
|
||||
uint32_t start = sys_now();
|
||||
asm volatile("sti");
|
||||
while (sys_now() - start < 5000) { // 5 second timeout
|
||||
while (sys_now() - start < 30000) { // 30 second timeout
|
||||
network_poll_internal();
|
||||
if (tcp_recv_queue) break;
|
||||
if (tcp_closed) break;
|
||||
@@ -281,7 +281,10 @@ int network_tcp_recv_nb(void *buf, size_t max_len) {
|
||||
if (network_processing) return -1;
|
||||
network_processing = 1;
|
||||
|
||||
network_poll_internal();
|
||||
|
||||
if (!tcp_recv_queue) {
|
||||
if (tcp_closed) { network_processing = 0; return -2; }
|
||||
network_processing = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -14,11 +14,19 @@ static void *gdt_memset(void *s, int c, size_t n) {
|
||||
return s;
|
||||
}
|
||||
|
||||
#define GDT_ENTRIES 7
|
||||
// Base GDT: 5 segments + 1 TSS (2 entries) = 7 entries for BSP.
|
||||
// For SMP: we add 2 entries per additional CPU for their TSS.
|
||||
// Max supported: 32 CPUs → 5 + 2*32 = 69 entries max.
|
||||
#define GDT_BASE_ENTRIES 5 // NULL, KCode, KData, UData, UCode
|
||||
#define GDT_MAX_ENTRIES 69 // 5 + 2*32
|
||||
|
||||
struct gdt_entry gdt[GDT_ENTRIES];
|
||||
struct gdt_entry gdt[GDT_MAX_ENTRIES];
|
||||
struct gdt_ptr gdtr;
|
||||
struct tss_entry tss;
|
||||
struct tss_entry tss; // BSP TSS (CPU 0)
|
||||
|
||||
// Per-CPU TSS array (dynamically allocated for AP cores)
|
||||
static struct tss_entry *ap_tss_array = NULL;
|
||||
static uint32_t ap_tss_count = 0;
|
||||
|
||||
extern void gdt_flush(uint64_t);
|
||||
extern void tss_flush(void);
|
||||
@@ -35,8 +43,8 @@ static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access,
|
||||
gdt[num].access = access;
|
||||
}
|
||||
|
||||
static void gdt_set_tss_gate(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
|
||||
// A TSS entry in x86_64 is 16 bytes (takes up 2 adjacent GDT entries)
|
||||
// Write a 16-byte TSS descriptor into GDT entries [num] and [num+1]
|
||||
static void gdt_set_tss_gate_at(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
|
||||
struct {
|
||||
uint16_t limit_low;
|
||||
uint16_t base_low;
|
||||
@@ -65,44 +73,98 @@ void tss_set_stack(uint64_t kernel_stack) {
|
||||
tss.rsp0 = kernel_stack;
|
||||
}
|
||||
|
||||
void tss_set_stack_cpu(uint32_t cpu_id, uint64_t kernel_stack) {
|
||||
if (cpu_id == 0) {
|
||||
tss.rsp0 = kernel_stack;
|
||||
} else if (ap_tss_array && cpu_id - 1 < ap_tss_count) {
|
||||
ap_tss_array[cpu_id - 1].rsp0 = kernel_stack;
|
||||
}
|
||||
}
|
||||
|
||||
void gdt_init(void) {
|
||||
gdtr.limit = (sizeof(struct gdt_entry) * GDT_ENTRIES) - 1;
|
||||
// Start with 7 entries (5 segments + BSP TSS taking 2)
|
||||
gdtr.limit = (sizeof(struct gdt_entry) * 7) - 1;
|
||||
gdtr.base = (uint64_t)&gdt;
|
||||
|
||||
// NULL segment
|
||||
gdt_set_gate(0, 0, 0, 0, 0);
|
||||
|
||||
// Kernel Code segment (Ring 0, 64-bit)
|
||||
// 0x9A: Present(1), Ring(0), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
|
||||
// 0xAF: Long Mode (64-bit) (L=1, DB=0)
|
||||
gdt_set_gate(1, 0, 0, 0x9A, 0xAF);
|
||||
|
||||
// Kernel Data segment (Ring 0)
|
||||
// 0x92: Present(1), Ring(0), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
|
||||
gdt_set_gate(2, 0, 0, 0x92, 0xAF);
|
||||
|
||||
// User Data segment (Ring 3)
|
||||
// 0xF2: Present(1), Ring(3), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
|
||||
gdt_set_gate(3, 0, 0, 0xF2, 0xAF);
|
||||
|
||||
// User Code segment (Ring 3, 64-bit)
|
||||
// 0xFA: Present(1), Ring(3), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
|
||||
// 0xAF: Long Mode (64-bit)
|
||||
gdt_set_gate(4, 0, 0, 0xFA, 0xAF);
|
||||
|
||||
// TSS segment (takes entries 5 and 6 technically because it's 16 bytes)
|
||||
// BSP TSS segment (entries 5 and 6)
|
||||
gdt_memset(&tss, 0, sizeof(struct tss_entry));
|
||||
tss.iopb_offset = sizeof(struct tss_entry);
|
||||
|
||||
// Allocate a default Ring 0 interrupt stack in case an interrupt fires early or
|
||||
// the scheduler hasn't set one up yet for a task.
|
||||
void* initial_tss_stack = kmalloc_aligned(4096, 4096);
|
||||
if (initial_tss_stack) {
|
||||
tss.rsp0 = (uint64_t)initial_tss_stack + 4096;
|
||||
}
|
||||
|
||||
gdt_set_tss_gate(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
||||
gdt_set_tss_gate_at(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
||||
|
||||
gdt_flush((uint64_t)&gdtr);
|
||||
tss_flush();
|
||||
}
|
||||
|
||||
// SMP: Add TSS entries for all AP cores and reload the GDT.
|
||||
void gdt_init_ap_tss(uint32_t cpu_count) {
|
||||
if (cpu_count <= 1) return; // No APs
|
||||
|
||||
uint32_t ap_count = cpu_count - 1;
|
||||
ap_tss_count = ap_count;
|
||||
|
||||
// Allocate per-CPU TSS structures
|
||||
ap_tss_array = (struct tss_entry *)kmalloc(ap_count * sizeof(struct tss_entry));
|
||||
if (!ap_tss_array) return;
|
||||
gdt_memset(ap_tss_array, 0, ap_count * sizeof(struct tss_entry));
|
||||
|
||||
// Each AP TSS goes at GDT slot 7 + (i*2) (since slot 5-6 is BSP TSS)
|
||||
for (uint32_t i = 0; i < ap_count; i++) {
|
||||
int gdt_slot = 7 + (i * 2);
|
||||
if (gdt_slot + 1 >= GDT_MAX_ENTRIES) break;
|
||||
|
||||
ap_tss_array[i].iopb_offset = sizeof(struct tss_entry);
|
||||
|
||||
// Allocate a kernel stack for this AP's interrupt handling
|
||||
void *ap_int_stack = kmalloc_aligned(8192, 4096);
|
||||
if (ap_int_stack) {
|
||||
ap_tss_array[i].rsp0 = (uint64_t)ap_int_stack + 8192;
|
||||
}
|
||||
|
||||
gdt_set_tss_gate_at(gdt_slot, (uint64_t)&ap_tss_array[i],
|
||||
sizeof(struct tss_entry) - 1, 0x89, 0x00);
|
||||
}
|
||||
|
||||
// Update GDT limit to include all new entries
|
||||
uint32_t total_entries = 7 + (ap_count * 2);
|
||||
if (total_entries > GDT_MAX_ENTRIES) total_entries = GDT_MAX_ENTRIES;
|
||||
gdtr.limit = (sizeof(struct gdt_entry) * total_entries) - 1;
|
||||
|
||||
// Reload GDTR on BSP with the expanded limit.
|
||||
// We must NOT call tss_flush() here — the BSP TSS is already loaded
|
||||
// and marked "busy" (0x8B). Trying to LTR a busy TSS causes GPF.
|
||||
asm volatile("lgdt %0" : : "m"(gdtr));
|
||||
}
|
||||
|
||||
// SMP: Load the TSS for a specific AP. Called from ap_entry().
|
||||
void gdt_load_ap_tss(uint32_t cpu_id) {
|
||||
if (cpu_id == 0) {
|
||||
// BSP uses slot 5 → selector 0x28
|
||||
asm volatile("mov $0x28, %%ax; ltr %%ax" ::: "ax");
|
||||
return;
|
||||
}
|
||||
|
||||
// AP cpu_id maps to GDT slot 7 + ((cpu_id-1) * 2)
|
||||
uint16_t selector = (uint16_t)((7 + ((cpu_id - 1) * 2)) * sizeof(struct gdt_entry));
|
||||
asm volatile("ltr %0" : : "r"(selector));
|
||||
}
|
||||
|
||||
@@ -48,4 +48,11 @@ struct gdt_ptr {
|
||||
void gdt_init(void);
|
||||
void tss_set_stack(uint64_t kernel_stack);
|
||||
|
||||
// SMP: Initialize per-CPU TSS entries. Call after smp detects cpu_count.
|
||||
void gdt_init_ap_tss(uint32_t cpu_count);
|
||||
// SMP: Load the TSS for a specific CPU (called from AP entry).
|
||||
void gdt_load_ap_tss(uint32_t cpu_id);
|
||||
// SMP: Set kernel stack for a specific CPU's TSS.
|
||||
void tss_set_stack_cpu(uint32_t cpu_id, uint64_t kernel_stack);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -59,7 +59,17 @@ uint64_t exception_handler_c(registers_t *regs) {
|
||||
serial_write(buf);
|
||||
|
||||
if ((regs->cs & 0x3) != 0) {
|
||||
serial_write("\nUSER MODE EXCEPTION - Terminating process.\n");
|
||||
serial_write("\n*** USER MODE EXCEPTION ***\nVector: 0x");
|
||||
k_itoa_hex(vector, buf);
|
||||
serial_write(buf);
|
||||
serial_write("\nRIP: 0x");
|
||||
k_itoa_hex(regs->rip, buf);
|
||||
serial_write(buf);
|
||||
serial_write("\nError Code: 0x");
|
||||
k_itoa_hex(regs->err_code, buf);
|
||||
serial_write(buf);
|
||||
serial_write("\nTerminating process.\n");
|
||||
|
||||
if (cmd_get_cursor_col() != 0) cmd_write("\n");
|
||||
cmd_write("*** USER EXCEPTION ***\nVector: "); cmd_write_hex(vector);
|
||||
cmd_write("\nRIP: "); cmd_write_hex(regs->rip);
|
||||
@@ -130,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) {
|
||||
@@ -230,6 +240,13 @@ void idt_register_interrupts(void) {
|
||||
idt_set_gate(30, exc30_wrapper, cs, 0x8E);
|
||||
idt_set_gate(31, exc31_wrapper, cs, 0x8E);
|
||||
|
||||
// SMP: Scheduling IPI for AP cores (vector 0x41 = 65)
|
||||
extern void isr_sched_ipi_wrapper(void);
|
||||
idt_set_gate(0x41, isr_sched_ipi_wrapper, cs, 0x8E);
|
||||
|
||||
// Syscall Handler (vector 128) - DPL 3 for user access
|
||||
extern void isr128_wrapper(void);
|
||||
idt_set_gate(128, isr128_wrapper, cs, 0xEE);
|
||||
}
|
||||
|
||||
void idt_load(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
|
||||
54
src/sys/lapic.c
Normal file
54
src/sys/lapic.c
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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 "lapic.h"
|
||||
#include "platform.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
|
||||
// LAPIC is at physical 0xFEE00000. Access via HHDM.
|
||||
static volatile uint32_t *lapic_base = 0;
|
||||
|
||||
// LAPIC register offsets (byte offsets, divided by 4 for uint32_t* indexing)
|
||||
#define LAPIC_ID (0x020 / 4)
|
||||
#define LAPIC_EOI (0x0B0 / 4)
|
||||
#define LAPIC_SVR (0x0F0 / 4)
|
||||
#define LAPIC_ICR_LOW (0x300 / 4)
|
||||
#define LAPIC_ICR_HIGH (0x310 / 4)
|
||||
|
||||
void lapic_enable(void) {
|
||||
if (!lapic_base) return;
|
||||
// Enable the LAPIC by setting the Spurious Interrupt Vector Register
|
||||
// Bit 8 = APIC Software Enable, vector = 0xFF (spurious)
|
||||
lapic_base[LAPIC_SVR] = 0x1FF;
|
||||
}
|
||||
|
||||
void lapic_init(void) {
|
||||
extern uint64_t hhdm_offset;
|
||||
lapic_base = (volatile uint32_t *)(hhdm_offset + 0xFEE00000ULL);
|
||||
|
||||
lapic_enable();
|
||||
serial_write("[LAPIC] Initialized at HHDM + 0xFEE00000\n");
|
||||
}
|
||||
|
||||
void lapic_eoi(void) {
|
||||
if (lapic_base) {
|
||||
lapic_base[LAPIC_EOI] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void lapic_send_ipi_all(void) {
|
||||
if (!lapic_base) return;
|
||||
|
||||
// Send IPI to all excluding self
|
||||
// ICR format:
|
||||
// bits 7:0 = vector (IPI_SCHED_VECTOR = 0x41)
|
||||
// bits 10:8 = delivery mode (000 = Fixed)
|
||||
// bit 11 = destination mode (0 = Physical)
|
||||
// bit 14 = level (1 = Assert)
|
||||
// bits 19:18 = destination shorthand (11 = All Excluding Self)
|
||||
uint32_t icr_low = IPI_SCHED_VECTOR | (0b11 << 18) | (1 << 14);
|
||||
|
||||
// Writing ICR_LOW triggers the IPI send
|
||||
lapic_base[LAPIC_ICR_LOW] = icr_low;
|
||||
}
|
||||
24
src/sys/lapic.h
Normal file
24
src/sys/lapic.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.
|
||||
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||
#ifndef LAPIC_H
|
||||
#define LAPIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// IPI vector used for scheduling on APs
|
||||
#define IPI_SCHED_VECTOR 0x41
|
||||
|
||||
// Initialize LAPIC access (maps registers via HHDM)
|
||||
void lapic_init(void);
|
||||
|
||||
// Enable LAPIC (set SVR bit 8)
|
||||
void lapic_enable(void);
|
||||
|
||||
// Send End-of-Interrupt to the local APIC
|
||||
void lapic_eoi(void);
|
||||
|
||||
// Send a scheduling IPI to all APs (excludes self)
|
||||
void lapic_send_ipi_all(void);
|
||||
|
||||
#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
|
||||
@@ -10,33 +10,41 @@
|
||||
#include "memory_manager.h"
|
||||
#include "elf.h"
|
||||
#include "wm.h"
|
||||
#include "spinlock.h"
|
||||
#include "smp.h"
|
||||
#include "lapic.h"
|
||||
|
||||
extern void cmd_write(const char *str);
|
||||
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 = NULL;
|
||||
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 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;
|
||||
|
||||
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;
|
||||
@@ -47,15 +55,42 @@ void process_init(void) {
|
||||
kernel_proc->used_memory = 32768; // Kernel stack
|
||||
|
||||
kernel_proc->next = kernel_proc; // Circular linked list
|
||||
current_process = kernel_proc;
|
||||
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;
|
||||
}
|
||||
|
||||
void process_create(void* entry_point, bool is_user) {
|
||||
if (process_count >= MAX_PROCESSES) return;
|
||||
process_t* process_create(void (*entry_point)(void), bool is_user) {
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
|
||||
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) {
|
||||
@@ -64,7 +99,10 @@ void process_create(void* entry_point, bool is_user) {
|
||||
new_proc->pml4_phys = paging_get_pml4_phys();
|
||||
}
|
||||
|
||||
if (!new_proc->pml4_phys) return;
|
||||
if (!new_proc->pml4_phys) {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 2. Allocate aligned stack
|
||||
void* user_stack = kmalloc_aligned(4096, 4096);
|
||||
@@ -131,30 +169,36 @@ void process_create(void* entry_point, bool is_user) {
|
||||
asm volatile("fninit");
|
||||
new_proc->fpu_initialized = true;
|
||||
|
||||
// Add to linked list (Critical Section)
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
new_proc->next = current_process->next;
|
||||
current_process->next = new_proc;
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
new_proc->cpu_affinity = 0; // Non-ELF processes stay on BSP
|
||||
|
||||
// Add to linked list
|
||||
new_proc->next = current_process[0]->next;
|
||||
current_process[0]->next = new_proc;
|
||||
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!new_proc) return NULL;
|
||||
if (!new_proc) {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_proc->pid = next_pid++;
|
||||
new_proc->is_user = true;
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
// 1. Setup Page Table
|
||||
new_proc->pml4_phys = paging_create_user_pml4_phys();
|
||||
@@ -166,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;
|
||||
}
|
||||
|
||||
@@ -315,64 +370,202 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
|
||||
asm volatile("fninit");
|
||||
new_proc->fpu_initialized = true;
|
||||
|
||||
// Add to linked list (Critical Section)
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
new_proc->next = current_process->next;
|
||||
current_process->next = new_proc;
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
// Assign to an AP core via round-robin (if SMP is active)
|
||||
uint32_t cpu_count = smp_cpu_count();
|
||||
if (cpu_count > 1) {
|
||||
new_proc->cpu_affinity = next_cpu_assign;
|
||||
next_cpu_assign++;
|
||||
if (next_cpu_assign >= cpu_count) next_cpu_assign = 1; // Wrap, skip CPU 0
|
||||
} else {
|
||||
new_proc->cpu_affinity = 0;
|
||||
}
|
||||
|
||||
serial_write("[PROCESS] Spawned ELF Executable: ");
|
||||
// Add to linked list (Critical Section)
|
||||
rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
new_proc->next = current_process[0]->next;
|
||||
current_process[0]->next = new_proc;
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
serial_write("[PROC] Exec: ");
|
||||
serial_write(filepath);
|
||||
serial_write("\n");
|
||||
return new_proc;
|
||||
}
|
||||
|
||||
process_t* process_get_current_for_cpu(uint32_t cpu_id) {
|
||||
if (cpu_id >= MAX_CPUS_SCHED) return NULL;
|
||||
return current_process[cpu_id];
|
||||
}
|
||||
|
||||
void process_set_current_for_cpu(uint32_t cpu_id, process_t* p) {
|
||||
if (cpu_id >= MAX_CPUS_SCHED) return;
|
||||
current_process[cpu_id] = p;
|
||||
}
|
||||
|
||||
process_t* process_get_current(void) {
|
||||
return current_process;
|
||||
uint32_t cpu = smp_this_cpu_id();
|
||||
return current_process[cpu];
|
||||
}
|
||||
|
||||
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[my_cpu]) {
|
||||
cleanup_pml4 = free_pml4_later[my_cpu];
|
||||
free_pml4_later[my_cpu] = 0;
|
||||
}
|
||||
|
||||
process_t *cur = current_process[my_cpu];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!current_process || !current_process->next || current_process == current_process->next)
|
||||
return current_rsp;
|
||||
}
|
||||
|
||||
// Save context
|
||||
current_process->rsp = current_rsp;
|
||||
cur->rsp = current_rsp;
|
||||
|
||||
// Switch to next ready process
|
||||
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();
|
||||
|
||||
process_t *start = current_process;
|
||||
process_t *next_proc = current_process->next;
|
||||
process_t *start = cur;
|
||||
process_t *next_proc = cur->next;
|
||||
|
||||
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 && !next_proc->kill_pending) {
|
||||
if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
next_proc = next_proc->next;
|
||||
}
|
||||
|
||||
current_process = next_proc;
|
||||
if (next_proc->cpu_affinity != my_cpu || next_proc->pid == 0xFFFFFFFF) {
|
||||
if (cur && cur->pid == 0xFFFFFFFF) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
|
||||
// Update Kernel Stack for User Mode interrupts and System Calls
|
||||
if (current_process->is_user && current_process->kernel_stack) {
|
||||
tss_set_stack(current_process->kernel_stack);
|
||||
extern uint64_t kernel_syscall_stack;
|
||||
kernel_syscall_stack = current_process->kernel_stack;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch page table
|
||||
paging_switch_directory(current_process->pml4_phys);
|
||||
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||
|
||||
current_process->ticks++;
|
||||
current_process[my_cpu]->ticks++;
|
||||
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||
|
||||
return current_process->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) {
|
||||
@@ -382,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;
|
||||
}
|
||||
@@ -403,6 +604,9 @@ static void process_cleanup_inner(process_t *proc) {
|
||||
extern void cmd_process_finished(void);
|
||||
cmd_process_finished();
|
||||
|
||||
extern void network_cleanup(void);
|
||||
network_cleanup();
|
||||
|
||||
extern void network_cleanup_pcb(void *pcb);
|
||||
// TODO: We need per-process PCB tracking to call this safely
|
||||
// For now, let's NOT call global network_cleanup
|
||||
@@ -411,8 +615,15 @@ 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;
|
||||
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
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);
|
||||
|
||||
@@ -424,31 +635,40 @@ void process_terminate(process_t *to_delete) {
|
||||
|
||||
if (prev == to_delete) {
|
||||
// Only one process (should be kernel), cannot terminate.
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Remove current from list
|
||||
prev->next = to_delete->next;
|
||||
|
||||
if (to_delete == current_process) {
|
||||
current_process = to_delete->next;
|
||||
// WARNING: If this was called as a regular function and not via a task switch,
|
||||
// the stack might be in a weird state. But usually we call this via window manager
|
||||
// or other external triggers.
|
||||
// Update per-CPU current_process if this was the current on any CPU
|
||||
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;
|
||||
while (np != to_delete) {
|
||||
if (np->cpu_affinity == c && np->pid != 0xFFFFFFFF) break;
|
||||
np = np->next;
|
||||
}
|
||||
if (np == to_delete || np->cpu_affinity != c) {
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (processes[i].pid == 0 || (processes[i].cpu_affinity == c && processes[i].is_user == false)) {
|
||||
np = &processes[i]; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
current_process[c] = np;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (to_delete->kernel_stack_alloc) {
|
||||
if (to_delete == current_process) {
|
||||
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
||||
} else {
|
||||
kfree(to_delete->kernel_stack_alloc);
|
||||
}
|
||||
}
|
||||
// Defer kernel stack until we switch away from it
|
||||
to_delete->kernel_stack_alloc = NULL;
|
||||
|
||||
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
|
||||
if (to_delete->pml4_phys && to_delete->is_user) {
|
||||
@@ -459,71 +679,91 @@ void process_terminate(process_t *to_delete) {
|
||||
to_delete->kernel_stack_alloc = NULL;
|
||||
to_delete->pml4_phys = 0;
|
||||
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
}
|
||||
|
||||
uint64_t process_terminate_current(void) {
|
||||
uint64_t rflags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
|
||||
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
|
||||
|
||||
if (!current_process || current_process->pid == 0) {
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
uint32_t my_cpu = smp_this_cpu_id();
|
||||
process_t *cur = current_process[my_cpu];
|
||||
|
||||
if (!cur || cur->pid == 0) {
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
process_cleanup_inner(current_process);
|
||||
process_cleanup_inner(cur);
|
||||
|
||||
// 2. Find previous process in circular list
|
||||
process_t *prev = current_process;
|
||||
while (prev->next != current_process) {
|
||||
process_t *prev = cur;
|
||||
while (prev->next != cur) {
|
||||
prev = prev->next;
|
||||
}
|
||||
|
||||
// 3. Remove current from list
|
||||
process_t *to_delete = current_process;
|
||||
process_t *to_delete = cur;
|
||||
|
||||
if (prev == current_process) {
|
||||
if (prev == cur) {
|
||||
// Only one process (should be kernel), cannot terminate.
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return to_delete->rsp;
|
||||
}
|
||||
|
||||
prev->next = to_delete->next;
|
||||
current_process = to_delete->next;
|
||||
|
||||
process_t *next_proc = to_delete->next;
|
||||
while (next_proc != to_delete) {
|
||||
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) break;
|
||||
next_proc = next_proc->next;
|
||||
}
|
||||
|
||||
if (next_proc == to_delete || 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;
|
||||
|
||||
// Mark slot as free
|
||||
to_delete->pid = 0xFFFFFFFF;
|
||||
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->is_user && current_process->kernel_stack) {
|
||||
tss_set_stack(current_process->kernel_stack);
|
||||
extern uint64_t kernel_syscall_stack;
|
||||
kernel_syscall_stack = current_process->kernel_stack;
|
||||
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->pml4_phys);
|
||||
paging_switch_directory(current_process[my_cpu]->pml4_phys);
|
||||
|
||||
// 5. Actually free the memory (after switching state to avoid issues)
|
||||
// We only safely free the user stack. Immediate freeing of the current
|
||||
// kernel stack is unsafe while we are still running on it.
|
||||
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);
|
||||
}
|
||||
|
||||
// Clear pointers to avoid double-free during slot reuse
|
||||
to_delete->user_stack_alloc = NULL;
|
||||
free_kernel_stack_later = to_delete->kernel_stack_alloc;
|
||||
to_delete->kernel_stack_alloc = NULL; // Leak the small kernel stack for safety
|
||||
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->rsp;
|
||||
asm volatile("push %0; popfq" : : "r"(rflags));
|
||||
uint64_t next_rsp = current_process[my_cpu]->rsp;
|
||||
spinlock_release_irqrestore(&runqueue_lock, rflags);
|
||||
return next_rsp;
|
||||
}
|
||||
|
||||
// SMP: IPI handler called on AP cores when BSP broadcasts scheduling IPI
|
||||
uint64_t sched_ipi_handler(registers_t *regs) {
|
||||
lapic_eoi(); // Acknowledge the IPI
|
||||
|
||||
// Run the scheduler for this CPU
|
||||
return process_schedule((uint64_t)regs);
|
||||
}
|
||||
|
||||
void process_push_gui_event(process_t *proc, gui_event_t *ev) {
|
||||
if (!proc) return;
|
||||
|
||||
@@ -556,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,6 +54,9 @@ typedef struct process {
|
||||
uint64_t ticks;
|
||||
uint64_t sleep_until;
|
||||
size_t used_memory;
|
||||
uint32_t cpu_affinity;
|
||||
bool is_idle;
|
||||
char cwd[1024];
|
||||
} __attribute__((aligned(16))) process_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -59,16 +64,23 @@ typedef struct {
|
||||
char name[64];
|
||||
uint64_t ticks;
|
||||
size_t used_memory;
|
||||
bool is_idle;
|
||||
} ProcessInfo;
|
||||
|
||||
void process_init(void);
|
||||
void process_create(void* entry_point, bool is_user);
|
||||
process_t* process_create_elf(const char* filepath, const char* args_str);
|
||||
process_t* process_create(void (*entry_point)(void), bool is_user);
|
||||
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);
|
||||
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
|
||||
uint64_t sched_ipi_handler(registers_t *regs);
|
||||
|
||||
void process_push_gui_event(process_t *proc, gui_event_t *ev);
|
||||
process_t* process_get_by_ui_window(void* win);
|
||||
|
||||
233
src/sys/smp.c
Normal file
233
src/sys/smp.c
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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 "smp.h"
|
||||
#include "limine.h"
|
||||
#include "memory_manager.h"
|
||||
#include "gdt.h"
|
||||
#include "idt.h"
|
||||
#include "platform.h"
|
||||
#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);
|
||||
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) {
|
||||
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 (!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].online && cpu_states[i].lapic_id == lapic) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smp_cpu_count(void) {
|
||||
return total_cpus;
|
||||
}
|
||||
|
||||
cpu_state_t *smp_get_cpu(uint32_t cpu_id) {
|
||||
if (cpu_id >= total_cpus) return NULL;
|
||||
return &cpu_states[cpu_id];
|
||||
}
|
||||
|
||||
static void ap_entry(struct limine_smp_info *info) {
|
||||
uint32_t my_id = (uint32_t)(info->extra_argument);
|
||||
uint64_t cr0;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1ULL << 2);
|
||||
cr0 |= (1ULL << 1);
|
||||
cr0 |= (1ULL << 5);
|
||||
asm volatile("mov %0, %%cr0" : : "r"(cr0));
|
||||
|
||||
uint64_t cr4;
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1ULL << 9);
|
||||
cr4 |= (1ULL << 10);
|
||||
asm volatile("mov %0, %%cr4" : : "r"(cr4));
|
||||
asm volatile("fninit");
|
||||
|
||||
extern struct gdt_ptr gdtr;
|
||||
extern void gdt_flush(uint64_t);
|
||||
gdt_flush((uint64_t)&gdtr);
|
||||
|
||||
gdt_load_ap_tss(my_id);
|
||||
|
||||
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);
|
||||
serial_write(" online (LAPIC ");
|
||||
serial_write_num(cpu_states[my_id].lapic_id);
|
||||
serial_write(")\n");
|
||||
|
||||
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) {
|
||||
total_cpus = 1;
|
||||
cpu_states = (cpu_state_t *)kmalloc(sizeof(cpu_state_t));
|
||||
if (!cpu_states) return 1;
|
||||
extern void mem_memset(void *, int, size_t);
|
||||
mem_memset(cpu_states, 0, sizeof(cpu_state_t));
|
||||
cpu_states[0].cpu_id = 0;
|
||||
cpu_states[0].lapic_id = read_lapic_id();
|
||||
cpu_states[0].online = true;
|
||||
serial_write("[SMP] Single CPU mode\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
total_cpus = (uint32_t)smp_resp->cpu_count;
|
||||
bsp_lapic_id = smp_resp->bsp_lapic_id;
|
||||
|
||||
serial_write("[SMP] Detected ");
|
||||
serial_write_num(total_cpus);
|
||||
serial_write(" CPUs. BSP LAPIC ID: ");
|
||||
serial_write_num(bsp_lapic_id);
|
||||
serial_write("\n");
|
||||
|
||||
cpu_states = (cpu_state_t *)kmalloc(total_cpus * sizeof(cpu_state_t));
|
||||
if (!cpu_states) {
|
||||
serial_write("[SMP] ERROR: Failed to allocate CPU state array!\n");
|
||||
total_cpus = 1;
|
||||
return 1;
|
||||
}
|
||||
extern void mem_memset(void *, int, size_t);
|
||||
mem_memset(cpu_states, 0, total_cpus * sizeof(cpu_state_t));
|
||||
|
||||
gdt_init_ap_tss(total_cpus);
|
||||
|
||||
uint32_t bsp_index = 0;
|
||||
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||
struct limine_smp_info *cpu = smp_resp->cpus[i];
|
||||
cpu_states[i].cpu_id = i;
|
||||
cpu_states[i].lapic_id = cpu->lapic_id;
|
||||
|
||||
if (cpu->lapic_id == bsp_lapic_id) {
|
||||
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 ");
|
||||
serial_write_num(cpu->lapic_id);
|
||||
serial_write(") online\n");
|
||||
} else {
|
||||
void *ap_stack = kmalloc_aligned(65536, 65536);
|
||||
if (!ap_stack) {
|
||||
serial_write("[SMP] ERROR: Failed to allocate AP stack!\n");
|
||||
continue;
|
||||
}
|
||||
cpu_states[i].kernel_stack = (uint64_t)ap_stack + 65536;
|
||||
cpu_states[i].kernel_stack_alloc = ap_stack;
|
||||
cpu_states[i].online = false;
|
||||
|
||||
cpu->extra_argument = i;
|
||||
|
||||
serial_write("[SMP] Starting AP ");
|
||||
serial_write_num(i);
|
||||
serial_write(" (LAPIC ");
|
||||
serial_write_num(cpu->lapic_id);
|
||||
serial_write(")...\n");
|
||||
|
||||
__atomic_store_n(&cpu->goto_address, ap_entry, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
}
|
||||
|
||||
volatile uint32_t timeout = 10000000;
|
||||
uint32_t online_count = 0;
|
||||
while (timeout-- > 0) {
|
||||
online_count = 0;
|
||||
for (uint32_t i = 0; i < total_cpus; i++) {
|
||||
if (cpu_states[i].online) online_count++;
|
||||
}
|
||||
if (online_count == total_cpus) break;
|
||||
asm volatile("pause");
|
||||
}
|
||||
|
||||
serial_write("[SMP] All ");
|
||||
serial_write_num(online_count);
|
||||
serial_write(" of ");
|
||||
serial_write_num(total_cpus);
|
||||
serial_write(" CPUs online\n");
|
||||
|
||||
return online_count;
|
||||
}
|
||||
34
src/sys/smp.h
Normal file
34
src/sys/smp.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 SMP_H
|
||||
#define SMP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "spinlock.h"
|
||||
|
||||
typedef struct cpu_state {
|
||||
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;
|
||||
|
||||
void smp_init_bsp(void);
|
||||
|
||||
|
||||
struct limine_smp_response;
|
||||
uint32_t smp_init(struct limine_smp_response *smp_resp);
|
||||
|
||||
uint32_t smp_this_cpu_id(void);
|
||||
|
||||
uint32_t smp_cpu_count(void);
|
||||
|
||||
cpu_state_t *smp_get_cpu(uint32_t cpu_id);
|
||||
|
||||
#endif
|
||||
62
src/sys/spinlock.h
Normal file
62
src/sys/spinlock.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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 SPINLOCK_H
|
||||
#define SPINLOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Simple test-and-set spinlock for x86_64 SMP.
|
||||
// Uses 'lock xchg' for acquire and a plain store for release.
|
||||
// Includes 'pause' to reduce bus contention while spinning.
|
||||
|
||||
typedef volatile uint32_t spinlock_t;
|
||||
|
||||
#define SPINLOCK_INIT 0
|
||||
|
||||
static inline void spinlock_acquire(spinlock_t *lock) {
|
||||
while (1) {
|
||||
// Try to set the lock from 0 -> 1
|
||||
uint32_t prev;
|
||||
asm volatile("lock xchgl %0, %1"
|
||||
: "=r"(prev), "+m"(*lock)
|
||||
: "0"((uint32_t)1)
|
||||
: "memory");
|
||||
if (prev == 0) return; // We got the lock
|
||||
// Spin with pause (reduces power and bus traffic)
|
||||
while (*lock) {
|
||||
asm volatile("pause" ::: "memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void spinlock_release(spinlock_t *lock) {
|
||||
asm volatile("" ::: "memory"); // compiler barrier
|
||||
*lock = 0;
|
||||
}
|
||||
|
||||
// Try to acquire without blocking. Returns 1 if acquired, 0 if not.
|
||||
static inline int spinlock_try(spinlock_t *lock) {
|
||||
uint32_t prev;
|
||||
asm volatile("lock xchgl %0, %1"
|
||||
: "=r"(prev), "+m"(*lock)
|
||||
: "0"((uint32_t)1)
|
||||
: "memory");
|
||||
return (prev == 0);
|
||||
}
|
||||
|
||||
// IRQ-safe spinlock: saves flags, disables interrupts, then acquires.
|
||||
// Use when the lock may be contended from interrupt context.
|
||||
static inline uint64_t spinlock_acquire_irqsave(spinlock_t *lock) {
|
||||
uint64_t flags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
|
||||
spinlock_acquire(lock);
|
||||
return flags;
|
||||
}
|
||||
|
||||
static inline void spinlock_release_irqrestore(spinlock_t *lock, uint64_t flags) {
|
||||
spinlock_release(lock);
|
||||
asm volatile("push %0; popfq" : : "r"(flags) : "memory");
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,20 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct Window Window;
|
||||
typedef struct registers_t registers_t;
|
||||
|
||||
typedef struct {
|
||||
char os_name[64];
|
||||
char os_version[64];
|
||||
char os_codename[64];
|
||||
char kernel_name[64];
|
||||
char kernel_version[64];
|
||||
char build_date[64];
|
||||
char build_time[64];
|
||||
char build_arch[64];
|
||||
} os_info_t;
|
||||
|
||||
// MSRs used for syscalls in x86_64
|
||||
#define MSR_EFER 0xC0000080
|
||||
#define MSR_STAR 0xC0000081
|
||||
@@ -42,10 +52,9 @@ typedef struct registers_t registers_t;
|
||||
#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_PARALLEL_RUN 50
|
||||
|
||||
void syscall_init(void);
|
||||
uint64_t syscall_handler_c(registers_t *regs);
|
||||
|
||||
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
|
||||
62
src/sys/work_queue.c
Normal file
62
src/sys/work_queue.c
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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 "work_queue.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
extern void serial_write(const char *str);
|
||||
|
||||
#define WORK_QUEUE_SIZE 64
|
||||
|
||||
static work_item_t work_queue[WORK_QUEUE_SIZE];
|
||||
static volatile int wq_head = 0;
|
||||
static volatile int wq_tail = 0;
|
||||
static spinlock_t wq_lock = SPINLOCK_INIT;
|
||||
|
||||
void work_queue_submit(work_fn_t fn, void *arg) {
|
||||
if (!fn) return;
|
||||
|
||||
uint64_t flags = spinlock_acquire_irqsave(&wq_lock);
|
||||
int next_tail = (wq_tail + 1) % WORK_QUEUE_SIZE;
|
||||
if (next_tail == wq_head) {
|
||||
// Queue full — drop the work item
|
||||
spinlock_release_irqrestore(&wq_lock, flags);
|
||||
return;
|
||||
}
|
||||
work_queue[wq_tail].fn = fn;
|
||||
work_queue[wq_tail].arg = arg;
|
||||
wq_tail = next_tail;
|
||||
spinlock_release_irqrestore(&wq_lock, flags);
|
||||
}
|
||||
|
||||
bool work_queue_drain_one(void) {
|
||||
uint64_t flags = spinlock_acquire_irqsave(&wq_lock);
|
||||
if (wq_head == wq_tail) {
|
||||
spinlock_release_irqrestore(&wq_lock, flags);
|
||||
return false;
|
||||
}
|
||||
work_item_t item = work_queue[wq_head];
|
||||
wq_head = (wq_head + 1) % WORK_QUEUE_SIZE;
|
||||
spinlock_release_irqrestore(&wq_lock, flags);
|
||||
|
||||
// Execute outside the lock
|
||||
if (item.fn) {
|
||||
item.fn(item.arg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void work_queue_drain_loop(void) {
|
||||
while (1) {
|
||||
// Try to drain all pending work
|
||||
while (work_queue_drain_one()) {
|
||||
// Keep draining
|
||||
}
|
||||
|
||||
// No work — halt the CPU until an interrupt wakes us.
|
||||
// With legacy PIC, APs don't receive timer interrupts, so they'll
|
||||
// sleep until an IPI is sent (e.g., when work is submitted).
|
||||
// This is ideal: APs use 0% CPU when idle.
|
||||
asm volatile("sti; hlt; cli");
|
||||
}
|
||||
}
|
||||
33
src/sys/work_queue.h
Normal file
33
src/sys/work_queue.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 WORK_QUEUE_H
|
||||
#define WORK_QUEUE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "spinlock.h"
|
||||
|
||||
// A simple work queue for offloading tasks to idle AP cores.
|
||||
// Producer (BSP or any core) calls work_queue_submit().
|
||||
// Consumer (AP idle loops) calls work_queue_drain_loop().
|
||||
|
||||
typedef void (*work_fn_t)(void *arg);
|
||||
|
||||
typedef struct {
|
||||
work_fn_t fn;
|
||||
void *arg;
|
||||
} work_item_t;
|
||||
|
||||
// Submit a work item. Thread-safe (uses spinlock internally).
|
||||
void work_queue_submit(work_fn_t fn, void *arg);
|
||||
|
||||
// Drain and execute all pending work items, then hlt until more arrive.
|
||||
// Called from AP idle loops. Never returns.
|
||||
void work_queue_drain_loop(void);
|
||||
|
||||
// Drain one item (if available). Returns true if work was done.
|
||||
// Useful for BSP to optionally help if idle.
|
||||
bool work_queue_drain_one(void);
|
||||
|
||||
#endif
|
||||
@@ -11,7 +11,7 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic
|
||||
BIN_DIR = bin
|
||||
|
||||
LIBC_SOURCES = $(wildcard libc/*.c)
|
||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o
|
||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o
|
||||
|
||||
VPATH = cli gui sys games libc net
|
||||
vpath %.c cli gui sys games libc net
|
||||
@@ -34,6 +34,9 @@ $(BIN_DIR)/crt0.o: crt0.asm
|
||||
$(BIN_DIR)/%.o: libc/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_DIR)/libwidget.o: ../wm/libwidget.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_DIR)/stb_image.o: stb_image.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
1455
src/userland/cli/bsh.c
Normal file
1455
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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user