Compare commits

...

159 commits
26.5.0 ... main

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

* updated copyright notices

bit of a hastle on my end, still getting used to being this cool
2026-05-14 15:22:32 +02:00
Lluciocc
86d05c6040
Merge pull request #48 from janevers-sys/main
Add GPL license header to find.c
2026-05-14 13:07:51 +02:00
Myles "Mellurboo" Wilson
a78bd424fc
Merge pull request #32 from Lluciocc/cmds
Adding time
2026-05-14 12:07:38 +01:00
Jan
faf56e56d9
Update copyright notice in find.c 2026-05-14 13:06:02 +02:00
Jan
478af73f63
Add GPL license header to find.c
Add copyright header and license information
2026-05-14 12:57:16 +02:00
Lluciocc
46566c766c
Update bsh.c 2026-05-14 10:20:56 +02:00
Lluciocc
7339183bf1
Add 'time' command to terminal usage documentation 2026-05-14 01:00:15 +02:00
Lluciocc
7d1426de46
Merge branch 'main' into cmds 2026-05-14 00:53:06 +02:00
Lluciocc
fdd25b31cd Feat: Implementing time command inside bsh.c 2026-05-14 00:47:47 +02:00
Lluciocc
59c3592c21
Merge pull request #45 from whitehai11/feat/sysfetch-uptime
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
Improve uptime display in sysfetch
2026-05-13 23:50:08 +02:00
whitehai11
bb57a4eeff Improve uptime display in sysfetch
Previously the uptime field only showed minutes. Now it displays
a more detailed and human-readable format:

  Uptime: 3 mins
  Uptime: 2 hours, 15 mins
  Uptime: 1 day, 3 hours, 22 mins
2026-05-13 23:43:30 +02:00
Lluciocc
3f19c74210
Merge pull request #37 from whitehai11/feat/add-grep-command
Add grep command
2026-05-13 23:19:25 +02:00
Lluciocc
5028e0e572
Merge pull request #44 from janevers-sys/main
Add find() command
2026-05-13 22:46:16 +02:00
Jan
05abd505ef
Improve comments for clarity in find.c
Clarified comments for variable 'type' and 'dir'.
2026-05-13 22:32:43 +02:00
Jan
3a295cd2f0
Create find.c 2026-05-13 22:24:26 +02:00
boreddevnl
4f17e0787c terminal: fix exit behavior by monitoring shell process status 2026-05-13 21:15:13 +02:00
boreddevnl
d4522cab21 Merge branch 'main' of https://github.com/BoredDevNL/BoredOS
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
2026-05-13 19:47:51 +02:00
boreddevnl
9c5cc83004 brand: Add Drift.png
Co-Authored-By: artemix1508 <273844106+artemix1508@users.noreply.github.com>
2026-05-13 19:21:48 +02:00
Lluciocc
007ef4469f
Merge pull request #42 from BoredDevNL/feat/shell-operators-and-tty-fds
feat: implement shell operators (|, >, >>, <, &, &&, ||, ;)
2026-05-13 16:14:06 +02:00
Lluciocc
d8b2e3d980
Merge pull request #41 from Mellurboo/main
Automatic Source File detection in Make
2026-05-13 16:05:10 +02:00
mellurboo
a9cfab53f9 Automatic Source File detection in Make 2026-05-13 14:54:54 +01:00
boreddevnl
74f7710ea0 feat: implement shell operators (|, >, >>, <, &, &&, ||, ;) and kernel-level TTY FDs 2026-05-13 15:51:13 +02:00
whitehai11
7a6769c2ec Extend grep with -r, -v, -l, -w and -x flags
Adds the following options to grep based on maintainer feedback:

- `-r` / `-R` — recursive search through directories
- `-v` — invert match, print non-matching lines
- `-l` — print only filenames that contain matches
- `-w` — match whole words only
- `-x` — match whole lines only

Multi-file output is automatically prefixed with the filename.
2026-05-13 15:05:27 +02:00
boreddevnl
f450ba4b51 doc: remove hard to maintain contributors table in README.md 2026-05-13 14:24:02 +02:00
Lluciocc
57bc840bcb
Clean up README.md by removing sections
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
2026-05-13 09:54:46 +02:00
Lluciocc
29e1b362ff replacing with strcmp && Adding comments && adding helper when the command don't exist 2026-05-13 09:17:08 +02:00
Lluciocc
5d0e828b41
asking testing platform and env in the PR template 2026-05-13 08:36:30 +02:00
Lluciocc
69d5f8feff Update header 2026-05-13 08:11:00 +02:00
Lluciocc
9574b99f40
Merge branch 'BoredDevNL:main' into cmds 2026-05-13 08:06:03 +02:00
Lluciocc
c66bfa62cf
Merge pull request #36 from zeyadhost/main
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
Add du command for disk usage reporting
2026-05-12 23:07:45 +02:00
Lluciocc
4b169b92de Update headers 2026-05-12 23:01:43 +02:00
zeyad
e75952e510 fix(cli): correct du usage format and replace -h with -H 2026-05-12 20:58:53 +00:00
whitehai11
e313e9dfcc feat(cli): add grep command
Implements grep <text> <file> for searching text inside files.

Supported options:
  -n    Show line numbers
  -i    Case-insensitive search
  -c    Print match count only
  -h    Show help

Closes part of #22
2026-05-12 22:57:15 +02:00
boreddevnl
f6141dfcaf Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-12 22:48:59 +02:00
boreddevnl
e813a6cdfd docs: add section for issue tags in bug report and feature request templates 2026-05-12 22:48:49 +02:00
zeyad
a6118e8d21 feat(cli): add du command for disk usage reporting 2026-05-12 20:33:09 +00:00
Chris
1ccc86ea41
doc: Add requirement for comments in PR template 2026-05-12 20:06:50 +02:00
Lluciocc
642dc7f8c9
Update toolchain.md with qemu-img PATH instruction
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
2026-05-12 19:28:28 +02:00
boreddevnl
43a62b025d Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-12 19:18:06 +02:00
boreddevnl
52b6532700 brand: add orbital.png
Co-Authored-By: Target <71287126+toiletalphamale@users.noreply.github.com>
2026-05-12 19:18:04 +02:00
Lluciocc
1655f1cf22
Add 'git' to required development tools installation 2026-05-12 19:17:00 +02:00
Lluciocc
5e3ba70730
Revise Windows toolchain installation instructions 2026-05-12 19:13:29 +02:00
boreddevnl
8d4ffd8a09 kernel: prevent infinite loop in process termination 2026-05-12 19:11:17 +02:00
Lluciocc
2580700ff9
Update time.c 2026-05-12 18:40:27 +02:00
Lluciocc
078ad437a5
Rename 'ptime' command to 'time' in help text 2026-05-12 18:33:08 +02:00
Lluciocc
c275da6145
Rename ptime to time 2026-05-12 18:32:36 +02:00
Lluciocc
0075493fba
Rename object files in Makefile to include 'libc_' prefix 2026-05-12 18:31:25 +02:00
Lluciocc
d007600e30
PR #34 from BoredDevNL: Implement df command and statfs support
Implement df command and statfs support
2026-05-12 18:08:54 +02:00
boreddevnl
a7cfb5d22d fix: stop format real human 2026-05-12 18:06:35 +02:00
boreddevnl
93811816fd Implement df command and statfs support 2026-05-12 17:06:47 +02:00
boreddevnl
13eaa7589d brand: remove 2 old wallpapers 2026-05-12 15:47:02 +02:00
boreddevnl
24b2754acb brand: Add The-Cat-Of-Destiny.png
Co-Authored-By: artemix1508 <273844106+artemix1508@users.noreply.github.com>
2026-05-12 15:21:08 +02:00
boreddevnl
a0e8521cf0 brand: add adrian.jpg
Co-Authored-By: QWR <196812618+qwroffc@users.noreply.github.com>
2026-05-12 15:19:28 +02:00
boreddevnl
16eedb752f doc: update contributors list 2026-05-12 14:30:28 +02:00
boreddevnl
6cf10fdbd3 brand: added squiggly.png
Co-Authored-By: pixelyblah <221841823+pixelyblah@users.noreply.github.com>
2026-05-12 14:26:43 +02:00
boreddevnl
7eaa9d278a brand: added Web-Of-Connectivity.png
Co-Authored-By: artemix1508 <273844106+artemix1508@users.noreply.github.com>
2026-05-12 14:26:08 +02:00
boreddevnl
45f26db141 brand: aded flowerdark/light.jpg
Co-Authored-By: QWR <196812618+qwroffc@users.noreply.github.com>
2026-05-12 14:25:07 +02:00
boreddevnl
801278cf73 feat: scrollable wallpaper page 2026-05-12 14:24:09 +02:00
boreddevnl
329fcf3bc0 doc: fix broken contributor list README.md 2026-05-12 11:37:52 +02:00
Lluciocc
04794bb986
doc: Update toochain steps on windows (#33)
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
* Create pull_request_template.md

Add a pull request template for better PR submissions.

* Document toolchain installation steps for Windows

* Add Table of Contents to toolchain.md
2026-05-12 10:01:47 +02:00
Lluciocc
11593b1b23
Add ptime command help message 2026-05-12 09:06:05 +02:00
Lluciocc
70ab1837c2
Add ptime 2026-05-12 09:04:27 +02:00
Lluciocc
96ddced34c
Merge branch 'BoredDevNL:main' into cmds 2026-05-12 08:56:11 +02:00
Myles "Mellurboo" Wilson
f171ff7278
pr: Standardize string functions for freestanding env #29
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
2026-05-11 22:10:58 +02:00
Lluciocc
51900ca0a7
Update syscalls id 2026-05-11 21:23:28 +02:00
boreddevnl
4141766c4f doc: Update contributors block README.md 2026-05-11 20:57:00 +02:00
boreddevnl
640c548a4b brand: Add boredshadow.png - Created by naplon74
Co-Authored-By: Naplon <94738563+naplon74@users.noreply.github.com>
2026-05-11 20:55:46 +02:00
Chris
0b31c5bb64
brand: Remove old (kind of ugly) wallpaper 2026-05-11 20:47:46 +02:00
Lluciocc
912bd4a20e
pr: Add ps && hexdump (#26)
* Adding hexdump and ps.c

* Add hexdump and ps command descriptions to help

* Update with missing ;
2026-05-11 20:28:16 +02:00
Lluciocc
fe1ba182d9
Update with missing ; 2026-05-11 20:25:35 +02:00
Lluciocc
2bb15d517f
pr: Create pull_request_template.md (#27)
Add a pull request template for better PR submissions.
2026-05-11 19:56:45 +02:00
boreddevnl
d45a19aac1 docs(appdev): update best practices for loop throttling 2026-05-11 19:52:48 +02:00
boreddevnl
a3a4494265 perf(userland): replace sys_yield with sleep for WAY better idle efficiency 2026-05-11 19:52:43 +02:00
boreddevnl
78a9afebf4 perf(terminal): optimize string rendering via color buffering 2026-05-11 19:52:18 +02:00
boreddevnl
309f68df48 feat(taskman): implement scrollbar and UI optimizations 2026-05-11 19:52:07 +02:00
boreddevnl
d3a353c9f8 legal: Add license header for zeyadhost 2026-05-11 19:38:50 +02:00
Lluciocc
f94384e572
Add hexdump and ps command descriptions to help 2026-05-11 19:36:27 +02:00
Lluciocc
ba281ea3f3
Adding hexdump and ps.c 2026-05-11 19:32:26 +02:00
zeyad
ed5f10eb7d
pr: Add lslbk disk listing command (#24)
Some checks are pending
Nightly Build / build-and-release (push) Waiting to run
* add lsblk disk listing command

* fix lsblk placeholder labels

* fix fat32 volume labels

* doc lsblk command

* add lsblk to help

* doc lsblk usage
2026-05-11 18:59:31 +02:00
boreddevnl
a01a34b7f2 dep: Depricate man.c 2026-05-11 18:54:34 +02:00
Lluciocc
e48f3674c7
pr: Update part of the docs (#17)
* Add cursor rendering section to window manager documentation

* Add cursor scale system commands to syscalls.md

* Add settings documentation for BoredOS

* Document cross-compiler build instructions for Linux

* Create README.md for BoredOS architecture documentation

* Update Architecture Overview link in README

* Reorganize Color Settings section in settings.md
2026-05-11 00:12:54 +02:00
boreddevnl
8a4ddb9b1e doc: add zeyadhost to README.md contributors list 2026-05-10 23:21:36 +02:00
boreddevnl
4e91a5619e Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-10 23:15:41 +02:00
boreddevnl
3691a2e1d6 doc: updated README feature set 2026-05-10 23:15:37 +02:00
zeyad
6993041a96
pr: Fix process slot exhaustion in process lifecycle (#16)
* fix process slot reuse after exit

* fix terminal tty reuse after close
2026-05-10 23:01:12 +02:00
boreddevnl
10018b62a3 Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-10 21:56:30 +02:00
boreddevnl
6784b2139f fix(net): improve network stability and resource management 2026-05-10 21:56:23 +02:00
boreddevnl
2d40ca30af fix(wm): implement window ownership and thread-safe glyph caching 2026-05-10 21:56:11 +02:00
boreddevnl
f5a79e451a feat(mem): harden slab allocator and fix paging race conditions 2026-05-10 21:55:50 +02:00
Myles "Mellurboo" Wilson
b85bb900e6
pr: ACPI Power Shutdown Implemented (#14)
* Flush PS/2 Devices on boot to avoid Locking dependent on the out buffer on real hardware / emulated PS2 over USB

Removed Slow and Unnessisarty flipping causing kconsole write slowdowns consequently speeding up the boot process

* sod wc

* ignoring dynamically created objects, added make run rule which will automatically detect the platform and then use the correct platform rule

* ACPI Power Shutdown
2026-05-10 21:16:54 +02:00
boreddevnl
77744464e3 legal: Remove redundant usage policy and boot prompt 2026-05-10 20:15:53 +02:00
boreddevnl
a452e5bee7 Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-10 19:33:05 +02:00
boreddevnl
9683971b0b doc: Update contributors block in README.md 2026-05-10 19:32:56 +02:00
Chris
8bbcea7d3e
doc: Add feature request template for Issues 2026-05-10 17:44:04 +02:00
Chris
19bbd1e9bb
doc: Add bug report template for Issues 2026-05-10 17:41:31 +02:00
boreddevnl
2bae90f797 doc: adjust usage.md for updated Makefile 2026-05-10 17:29:34 +02:00
boreddevnl
2be30b3056 doc: Add Discord invite to README.md 2026-05-10 17:29:19 +02:00
boreddevnl
a508da4ac8 test: add extra line to test webhook 2026-05-10 16:50:50 +02:00
boreddevnl
2d6c8a2947 Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-10 16:47:04 +02:00
boreddevnl
0a8a5877db doc: remove emoji from features section in README.md (webhook test lol) 2026-05-10 16:46:57 +02:00
Myles "Mellurboo" Wilson
6fa2aa453b
pr: .gitignore changes & make run generic rule with platform detection (#13)
* Flush PS/2 Devices on boot to avoid Locking dependent on the out buffer on real hardware / emulated PS2 over USB

Removed Slow and Unnessisarty flipping causing kconsole write slowdowns consequently speeding up the boot process

* sod wc

* ignoring dynamically created objects, added make run rule which will automatically detect the platform and then use the correct platform rule
2026-05-10 16:36:34 +02:00
boreddevnl
066f6740e5 art: Add Bored Bird
Co-authored-by: Artemix <artemix1508@users.noreply.github.com>
2026-05-10 11:30:12 +02:00
Myles "Mellurboo" Wilson
fdcfd48a24
pr: Removed Unnessisary Flipping on kconsole leading to Vastly Faster Boot Times & PS/2 Hardware Correctness (#12)
* Flush PS/2 Devices on boot to avoid Locking dependent on the out buffer on real hardware / emulated PS2 over USB

Removed Slow and Unnessisarty flipping causing kconsole write slowdowns consequently speeding up the boot process

* sod wc
2026-05-10 11:22:18 +02:00
boreddevnl
e9888f26b1 feat: add datetime and cpu list to sysfetch 2026-05-09 23:54:18 +02:00
boreddevnl
97c5c3ffdf feat: add datetime to procfs 2026-05-09 23:53:50 +02:00
boreddevnl
e80f4ba172 doc: prettier README 2026-05-09 23:43:32 +02:00
boreddevnl
3d53e45e20 fix: doom inputs 2026-05-09 23:14:19 +02:00
boreddevnl
3bb90a8b7f fix(input): refine legacy key mapping and ensure codepoints on release 2026-05-09 22:30:53 +02:00
boreddevnl
963e68162b mv: rename night_mountain.jpg --> volcano.jpg 2026-05-09 22:20:13 +02:00
boreddevnl
5a7ef42b4a brand: replace old icon 2026-05-09 22:19:51 +02:00
boreddevnl
85d06482fc feat: keymap_legacy_key support in doomgeneric 2026-05-09 22:19:28 +02:00
boreddevnl
0ff2f2b07e feat: support multiple image formats in wallpaper chooser 2026-05-09 22:18:51 +02:00
boreddevnl
4ed39f6a0c fix: slow code from llucioc >:( 2026-05-09 22:08:05 +02:00
boreddevnl
f1cee25e62 Merge branch 'main' of https://github.com/BoredDevNL/BoredOS 2026-05-09 21:55:51 +02:00
boreddevnl
0557174a67 feat: Add seperate runtime parameters for run-hd for windows and linux 2026-05-09 21:55:36 +02:00
Lluciocc
cfae88f9f5
PR: Fixing settings && Adding a option for cursor scale (#11)
* Adding slider widget

* feat: Add mouse cursor scale commands and settings improvement speed

* Correct wallpaper scanning and thumbnail loading
2026-05-09 21:53:07 +02:00
boreddevnl
36ed0d4a9e fix: ensure partitions are recognized after fdisk and fix AHCI slot bug 2026-05-09 21:51:28 +02:00
boreddevnl
82226ddbe5 actions: auto run on pushed commit 2026-05-09 21:37:46 +02:00
boreddevnl
4f3ac28401 brand: change boot splash logo 2026-05-09 21:36:31 +02:00
boreddevnl
fbd66a804e lib: Copy stb_image into TCC lib 2026-05-09 19:29:18 +02:00
boreddevnl
a47c4aa841 Add co-author credit for art
Co-authored-by: Artemix <artemix1508@users.noreply.github.com>
2026-05-09 19:03:51 +02:00
boreddevnl
1e91395eaa build: modernize initrd structure and asset management 2026-05-09 18:54:51 +02:00
boreddevnl
5c2347d5d1 ui: replace legacy ASCII logos with graphical assets 2026-05-09 18:54:42 +02:00
boreddevnl
2784c36e84 wm: add support for graphical logo 2026-05-09 18:54:20 +02:00
boreddevnl
8fcf0f67a4 brand: New boredOS ascii art 2026-05-09 18:52:20 +02:00
boreddevnl
8ecc3e7b0c brand: New branding and wallpaper 2026-05-09 18:51:55 +02:00
boreddevnl
e639c6884c doc: new icons, contributor and README refresh 2026-05-09 18:51:27 +02:00
boreddevnl
30e8ed22a0 doc: Remove outdated header. 2026-05-09 17:27:55 +02:00
boreddevnl
ff9be87c5d perf: optimize graphics rendering and implement caching 2026-05-09 17:27:39 +02:00
boreddevnl
eb05d62891 feat(viewer): improve GIF loading and system stability 2026-05-09 17:27:27 +02:00
boreddevnl
aedf72eeef ignore: Static .a libraries 2026-05-09 17:25:40 +02:00
boreddevnl
679b2ae878 actions: fix indentation 2026-05-09 03:16:06 +02:00
boreddevnl
d25e23f999 actions: script fix 2026-05-09 03:14:27 +02:00
boreddevnl
08e6482f08 actions: fix build script 2026-05-09 03:09:32 +02:00
boreddevnl
14e38a5221 build: merge remaining submodules and clean up .gitignore 2026-05-09 01:56:30 +02:00
boreddevnl
93722faa91 userland: replace legacy cc with native TCC and vendor submodule 2026-05-09 01:54:09 +02:00
boreddevnl
e2ecef39e6 doc: TCC 2026-05-09 01:17:55 +02:00
boreddevnl
3e52d8c8fc .gitignore: undo src/userland/cli/third_party/tcc/ removal 2026-05-09 01:12:32 +02:00
boreddevnl
5fa713c514 .gitignore: src/userland/cli/third_party/tcc/ 2026-05-09 01:11:58 +02:00
boreddevnl
230e404a98 feature: added third_party TCC into userland 2026-05-09 01:11:29 +02:00
boreddevnl
a62b22faa9 app: Resizable txtedit.c 2026-05-09 01:10:21 +02:00
boreddevnl
8fd0605f85 doc: Remove unnecesary note
Co-authored-by: Copilot <copilot@github.com>
2026-05-08 21:08:56 +02:00
boreddevnl
480149a94e ignore edk2-vars.fd 2026-05-08 21:07:21 +02:00
boreddevnl
274ee54814 docs: update syscall reference and add installation guides 2026-05-08 21:06:07 +02:00
boreddevnl
6e1eb7768d core: update system boot logic for disk-based root and cmdline flags 2026-05-08 21:05:33 +02:00
boreddevnl
0fbc3a5fc8 wm: implement root filesystem pivoting and persistence provisioning 2026-05-08 21:04:58 +02:00
boreddevnl
5ae8c56d40 libc: add disk management syscalls and expanded stdio support 2026-05-08 21:04:35 +02:00
boreddevnl
91edd3bc78 feature(install): add system installer and FAT32 formatting utility 2026-05-08 21:03:51 +02:00
boreddevnl
fd18369bd7 disk: add GPT/MBR writing and disk rescan support 2026-05-08 21:02:47 +02:00
boreddevnl
3e26332b1a fs: implement FAT32 LFN support and root volume abstraction 2026-05-08 21:02:14 +02:00
boreddevnl
b04bde3d9e legal: Usage policy V2.0 2026-05-04 23:20:50 +02:00
boreddevnl
d854d0e50f ver: bump OS and kernel to x.x.1
Note: versions >= x.x.1 require users to comply with POLICY.md.
2026-05-04 22:54:14 +02:00
boreddevnl
5b7940dd04 legal: Add usage policy for current and future commits.
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 19:45:36 +02:00
boreddevnl
b486bb2ca5 ver: 4.2.0-dev --> 4.2.0-stable 2026-05-01 20:57:12 +02:00
boreddevnl
7ae1d40e41 ver: 26.5-dev --> 26.5-stable 2026-05-01 20:56:19 +02:00
742 changed files with 160298 additions and 3084 deletions

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**1. System/Environment:**
* Are you running on QEMU, VirtualBox, VMware, or Bare Metal?
* What OS are you compiling from? (e.g., Ubuntu 22.04, Windows/WSL2)
* Which branch/commit of BoredOS are you on?
**2. Describe the Bug:**
A clear and concise description of what the bug is. What did you expect to happen, and what actually happened?
**3. Steps to Reproduce:**
1. Boot the OS using 'make run'
2. Open application 'X'
3. Click on 'Y'
4. See error...
**4. Logs and Screenshots:**
* Please attach screenshots of the kernel panic, GUI glitch, or terminal output.
* If you have serial logs, attach them as a `.txt` file or use a code block. Do NOT paste 500 lines of logs directly into the chat!
**5. Additional Context:**
Add any other context about the problem here (e.g., "This only happens when my mouse is moving").
**6. Please add tags to your issue to help with organization.**

View file

@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**1. Is your feature request related to a problem?**
A clear and concise description of what the problem is. (e.g., "I'm always frustrated when I can't save files persistently across reboots...")
**2. Describe the Solution you'd like:**
A clear and concise description of what you want to happen. Are you proposing a new syscall, a new userspace app, or a kernel change?
**3. Describe alternatives you've considered:**
A clear and concise description of any alternative solutions or features you've considered. (e.g., "Instead of ext2, we could just implement FAT32 first.")
**4. Can you help build it?**
Let us know if this is just an idea you'd like to see, or if you plan on submitting a Pull Request for it yourself!
**5. Additional Context:**
Add any other context, mockup screenshots, or links to technical documentation (e.g., OSDev Wiki links) here.
**6. Please add tags to your issue to help with organization.**

36
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,36 @@
## Description
Describe the changes made in this PR.
---
## Testing
- [ ] Code has been tested
- [ ] Existing tests pass
### Platform / Environment
What platform and environment were used for development and testing?
Examples:
- Windows 11 / macOS / Linux
- MSYS2 / WSL2 / Debian
Notes:
<!-- Add anything relevant about testing -->
---
## Documentation
- [ ] Code contains appropriate comments (REQUIRED for medium to large PR's.)
- [ ] Documentation updated if needed
Notes:
<!-- Add anything relevant about documentation -->
---
## Additional Notes
<!-- Anything reviewers should know -->

View file

@ -1,13 +1,13 @@
name: Nightly Build name: Nightly Build
on: on:
push:
branches:
- 'main'
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: write contents: write
jobs: jobs:
build-and-release: build-and-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -26,6 +26,7 @@ jobs:
xorriso xorriso
sudo ln -sf /usr/bin/x86_64-linux-gnu-gcc /usr/local/bin/x86_64-elf-gcc sudo ln -sf /usr/bin/x86_64-linux-gnu-gcc /usr/local/bin/x86_64-elf-gcc
sudo ln -sf /usr/bin/x86_64-linux-gnu-ld /usr/local/bin/x86_64-elf-ld sudo ln -sf /usr/bin/x86_64-linux-gnu-ld /usr/local/bin/x86_64-elf-ld
sudo ln -sf /usr/bin/x86_64-linux-gnu-ar /usr/local/bin/x86_64-elf-ar
- name: Build ISO - name: Build ISO
run: make -j4 run: make -j4
@ -49,7 +50,6 @@ jobs:
name: Nightly Build (${{ steps.metadata.outputs.short_sha }}) name: Nightly Build (${{ steps.metadata.outputs.short_sha }})
body: | body: |
This is an automated nightly build of BoredOS, this is not a final release and may be unstable. This is an automated nightly build of BoredOS, this is not a final release and may be unstable.
Built from commit: Built from commit:
- Full hash: `${{ github.sha }}` - Full hash: `${{ github.sha }}`
- Short hash: `${{ steps.metadata.outputs.short_sha }}` - Short hash: `${{ steps.metadata.outputs.short_sha }}`

45
.gitignore vendored
View file

@ -1,33 +1,24 @@
.git 2/FETCH_HEAD # Build artifacts
limine 2/BOOTAA64.EFI /build/
limine 2/BOOTIA32.EFI
limine 2/BOOTRISCV64.EFI
limine 2/BOOTX64.EFI
limine 2/install-sh
limine 2/LICENSE
limine 2/limine-bios-cd.bin
limine 2/limine-bios-hdd.h
limine 2/limine-bios-pxe.bin
limine 2/limine-bios.sys
limine 2/limine-uefi-cd.bin
limine 2/limine.c
limine 2/limine.dSYM/Contents/Info.plist
limine 2/limine.dSYM/Contents/Resources/Relocations/aarch64/limine.yml
limine 2/limine.h
limine 2/Makefile
limine 2/limine
limine 2/limine.dSYM/Contents/Resources/DWARF/limine
limine 2/limine.exe
boredos.dump
qemu-debug.log
iso_root/ iso_root/
limine/
src/userland/bin/
boredos.iso boredos.iso
disk.img disk.img
limine disk.qcow2
edk2-vars.fd
qemu-debug.log
boredos.dump
# Userland
src/userland/bin/
# Temporary files
**/.DS_Store **/.DS_Store
.DS_Store .DS_Store
/build/
*.o *.o
disk.img *.a
# Others
.gitignore
src/userland/cli/third_party/tcc/tcc.elf
src/userland/sdk/include/*
limine

0
.gitmodules vendored Normal file
View file

368
Makefile
View file

@ -1,10 +1,9 @@
# BoredOS Makefile
# Target Architecture: x86_64
# Host: macOS
# Copyright (c) 2023-2026 Chris (boreddevnl) # Copyright (c) 2023-2026 Chris (boreddevnl)
# This software is released under the GNU General Public License v3.0. See LICENSE file for details. # This software is released under the GNU General Public License v3.0. See LICENSE file for details.
# This header needs to maintain in any file it is present in, as per the GPL license terms. # This header needs to maintain in any file it is present in, as per the GPL license terms.
export MAKEFLAGS += -j4
CC = x86_64-elf-gcc CC = x86_64-elf-gcc
LD = x86_64-elf-ld LD = x86_64-elf-ld
NASM = nasm NASM = nasm
@ -39,37 +38,21 @@ USERLAND_METADATA_ICONS = $(shell { \
} | tr ';' '\n' | sed 's@.*/@@' | sed '/^[[:space:]]*$$/d' | sort -u) } | tr ';' '\n' | sed 's@.*/@@' | sed '/^[[:space:]]*$$/d' | sort -u)
COLLOID_ICONS = $(sort $(DOCK_COLLOID_ICONS) $(USERLAND_COLLOID_ICONS) $(USERLAND_METADATA_ICONS) xterm.png) COLLOID_ICONS = $(sort $(DOCK_COLLOID_ICONS) $(USERLAND_COLLOID_ICONS) $(USERLAND_METADATA_ICONS) xterm.png)
C_SOURCES = $(wildcard $(SRC_DIR)/core/*.c) \ C_SOURCES := $(shell find $(SRC_DIR) -type f -name '*.c' \
$(wildcard $(SRC_DIR)/sys/*.c) \ ! -path '$(SRC_DIR)/userland/*' \
$(wildcard $(SRC_DIR)/mem/*.c) \ ! -path '*/third_party/lwip/netif/slipif.c')
$(wildcard $(SRC_DIR)/dev/*.c) \ ASM_SOURCES := $(shell find $(SRC_DIR) -type f -name '*.asm' ! -path '$(SRC_DIR)/userland/*')
$(wildcard $(SRC_DIR)/input/*.c) \
$(wildcard $(SRC_DIR)/net/*.c) \
$(wildcard $(SRC_DIR)/net/nic/*.c) \
$(wildcard $(SRC_DIR)/fs/*.c) \
$(wildcard $(SRC_DIR)/wm/*.c) \
$(wildcard $(SRC_DIR)/net/third_party/lwip/core/*.c) \
$(wildcard $(SRC_DIR)/net/third_party/lwip/core/ipv4/*.c) \
$(SRC_DIR)/net/third_party/lwip/netif/ethernet.c \
$(SRC_DIR)/net/third_party/lwip/netif/bridgeif.c
ASM_SOURCES = $(wildcard $(SRC_DIR)/arch/*.asm) OBJ_FILES := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(C_SOURCES)) \
OBJ_FILES = $(patsubst $(SRC_DIR)/core/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/core/*.c)) \ $(patsubst $(SRC_DIR)/%.asm, $(BUILD_DIR)/%.o, $(ASM_SOURCES))
$(patsubst $(SRC_DIR)/sys/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/sys/*.c)) \
$(patsubst $(SRC_DIR)/mem/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/mem/*.c)) \ INCLUDE_DIRS := $(shell find $(SRC_DIR) -type d ! -path '$(SRC_DIR)/userland*')
$(patsubst $(SRC_DIR)/dev/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/dev/*.c)) \ INCLUDES := $(patsubst %, -I%, $(INCLUDE_DIRS))
$(patsubst $(SRC_DIR)/input/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/input/*.c)) \
$(patsubst $(SRC_DIR)/net/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/*.c)) \
$(patsubst $(SRC_DIR)/net/nic/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/net/nic/*.c)) \
$(patsubst $(SRC_DIR)/fs/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/fs/*.c)) \
$(patsubst $(SRC_DIR)/wm/%.c, $(BUILD_DIR)/%.o, $(wildcard $(SRC_DIR)/wm/*.c)) \
$(patsubst $(SRC_DIR)/net/third_party/lwip/%.c, $(BUILD_DIR)/lwip/%.o, $(filter $(SRC_DIR)/net/third_party/lwip/%.c, $(C_SOURCES))) \
$(patsubst $(SRC_DIR)/arch/%.asm, $(BUILD_DIR)/%.o, $(ASM_SOURCES))
CFLAGS = -g -O2 -pipe -Wall -Wextra -std=gnu11 -ffreestanding \ CFLAGS = -g -O2 -pipe -Wall -Wextra -std=gnu11 -ffreestanding \
-fno-stack-protector -fno-stack-check -fno-lto -fPIE \ -fno-stack-protector -fno-stack-check -fno-lto -fPIE \
-m64 -march=x86-64 -msse -msse2 -mstackrealign -mno-red-zone \ -m64 -march=x86-64 -msse -msse2 -mstackrealign -mno-red-zone \
-I$(SRC_DIR) -I$(SRC_DIR)/net/third_party/lwip -I$(SRC_DIR)/core -I$(SRC_DIR)/sys -I$(SRC_DIR)/mem -I$(SRC_DIR)/dev -I$(SRC_DIR)/net -I$(SRC_DIR)/net/nic -I$(SRC_DIR)/fs -I$(SRC_DIR)/wm -I$(SRC_DIR)/input $(INCLUDES)
LDFLAGS = -m elf_x86_64 -nostdlib -static -pie --no-dynamic-linker \ LDFLAGS = -m elf_x86_64 -nostdlib -static -pie --no-dynamic-linker \
-z text -z max-page-size=0x1000 -T linker.ld -z text -z max-page-size=0x1000 -T linker.ld
@ -79,7 +62,9 @@ NASMFLAGS = -f elf64
LIMINE_VERSION = 10.8.2 LIMINE_VERSION = 10.8.2
LIMINE_URL_BASE = https://github.com/limine-bootloader/limine/raw/v$(LIMINE_VERSION) LIMINE_URL_BASE = https://github.com/limine-bootloader/limine/raw/v$(LIMINE_VERSION)
.PHONY: all clean run limine-setup run-windows run-mac run-linux HOST_OS := $(shell uname -s 2>/dev/null || echo Windows)
.PHONY: all clean run run-hd limine-setup run-windows run-mac run-linux run-hd-mac run-hd-windows run-hd-linux
all: all:
$(call PRINT_STEP,STARTING BOREDOS BUILD) $(call PRINT_STEP,STARTING BOREDOS BUILD)
@ -89,228 +74,223 @@ all:
$(BUILD_DIR): $(BUILD_DIR):
$(call PRINT_STEP,CREATING BUILD DIRECTORY) $(call PRINT_STEP,CREATING BUILD DIRECTORY)
mkdir -p $(BUILD_DIR) mkdir -p $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
limine-setup: limine-setup:
$(call PRINT_STEP,SETTING UP LIMINE) $(call PRINT_STEP,SETTING UP LIMINE)
@if [ ! -f limine/limine-bios.sys ]; then \ @if [ ! -f limine/limine-bios.sys ]; then \
printf "$(YELLOW)[LIMINE] Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary...$(RESET)"; \ printf "$(YELLOW)[LIMINE] Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary...$(RESET)\n"; \
rm -rf limine; \ rm -rf limine; \
git clone https://github.com/limine-bootloader/limine.git --branch=v$(LIMINE_VERSION)-binary --depth=1 limine; \ git clone https://github.com/limine-bootloader/limine.git --branch=v$(LIMINE_VERSION)-binary --depth=1 limine; \
else \ else \
printf "$(YELLOW)[LIMINE] Existing Limine binaries found.$(RESET)"; \ printf "$(YELLOW)[LIMINE] Existing Limine binaries found.$(RESET)\n"; \
fi fi
@if [ ! -f $(SRC_DIR)/core/limine.h ]; then \ @if [ ! -f $(SRC_DIR)/core/limine.h ]; then \
printf "$(YELLOW)[LIMINE] Copying limine.h...$(RESET)"; \ printf "$(YELLOW)[LIMINE] Copying limine.h...$(RESET)\n"; \
cp limine/limine.h $(SRC_DIR)/core/limine.h; \ cp limine/limine.h $(SRC_DIR)/core/limine.h; \
else \ else \
printf "$(YELLOW)[LIMINE] limine.h already present.$(RESET)"; \ printf "$(YELLOW)[LIMINE] limine.h already present.$(RESET)\n"; \
fi fi
@printf "$(YELLOW)[LIMINE] Building Limine host utility...$(RESET)" @printf "$(YELLOW)[LIMINE] Building Limine host utility...$(RESET)\n"
$(MAKE) -C limine $(MAKE) -C limine
@printf "$(GREEN)[OK] Limine setup complete.$(RESET)" @printf "$(GREEN)[OK] Limine setup complete.$(RESET)\n"
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET) $< -> $@" @printf "$(YELLOW)[CC]$(RESET) $< -> $@\n"
mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/core/%.c | $(BUILD_DIR) limine-setup $(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR)
@printf "$(YELLOW)[CC]$(RESET)[core] $< -> $@" @printf "$(YELLOW)[ASM]$(RESET) $< -> $@\n"
mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/sys/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[sys] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/mem/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[mem] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/dev/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[dev] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/input/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[input] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/net/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[net] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/net/nic/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[net/nic] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/fs/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[fs] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/wm/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[wm] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/lwip/%.o: $(SRC_DIR)/net/third_party/lwip/%.c | $(BUILD_DIR) limine-setup
@printf "$(YELLOW)[CC]$(RESET)[lwIP] $< -> $@"
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/arch/%.asm | $(BUILD_DIR)
@printf "$(YELLOW)[ASM]$(RESET) $< -> $@"
$(NASM) $(NASMFLAGS) $< -o $@ $(NASM) $(NASMFLAGS) $< -o $@
$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/arch/test_syscall.asm | $(BUILD_DIR)
@printf "$(YELLOW)[ASM][test_syscall]$(RESET) $< -> $@"
$(NASM) $(NASMFLAGS) $< -o $@
$(BUILD_DIR)/user_test.o: $(SRC_DIR)/arch/user_test.asm | $(BUILD_DIR)
@printf "$(YELLOW)[ASM][user_test]$(RESET) $< -> $@"
$(NASM) $(NASMFLAGS) $< -o $@
$(BUILD_DIR)/process_asm.o: $(SRC_DIR)/arch/process_asm.asm | $(BUILD_DIR)
@printf "$(YELLOW)[ASM][process]$(RESET) $< -> $@"
$(NASM) $(NASMFLAGS) $< -o $@
$(KERNEL_ELF): $(OBJ_FILES) $(KERNEL_ELF): $(OBJ_FILES)
$(call PRINT_STEP,LINKING KERNEL) $(call PRINT_STEP,LINKING KERNEL)
@printf "$(YELLOW)[LD]$(RESET) Linking kernel ELF: $@" @printf "$(YELLOW)[LD]$(RESET) Linking kernel ELF: $@\n"
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES) $(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
@printf "$(GREEN)[OK]$(RESET) Kernel ELF built: $@" @printf "$(GREEN)[OK]$(RESET) Kernel ELF built: $@\n"
$(call PRINT_STEP,BUILDING USERLAND) $(call PRINT_STEP,BUILDING USERLAND)
$(MAKE) -C $(SRC_DIR)/userland $(MAKE) -C $(SRC_DIR)/userland
@printf "$(GREEN)[OK]$(RESET) Userland build complete." @printf "$(GREEN)[OK]$(RESET) Userland build complete.\n"
$(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
$(call PRINT_STEP,BUILDING INITRD) $(call PRINT_STEP,BUILDING INITRD)
@printf "$(YELLOW)[INITRD]$(RESET) Cleaning previous initrd directory..." @printf "$(YELLOW)[INITRD]$(RESET) Cleaning previous initrd directory...\n"
rm -rf $(BUILD_DIR)/initrd rm -rf $(BUILD_DIR)/initrd
@printf "$(YELLOW)[INITRD]$(RESET) Creating directory structure..." @printf "$(YELLOW)[INITRD]$(RESET) Creating directory structure...\n"
mkdir -p $(BUILD_DIR)/initrd/bin mkdir -p $(BUILD_DIR)/initrd/bin
mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers mkdir -p $(BUILD_DIR)/initrd/Library/images/Wallpapers
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
mkdir -p $(BUILD_DIR)/initrd/Library/images/icons/colloid mkdir -p $(BUILD_DIR)/initrd/Library/images/icons/colloid
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
mkdir -p $(BUILD_DIR)/initrd/Library/conf
mkdir -p $(BUILD_DIR)/initrd/Library/bsh mkdir -p $(BUILD_DIR)/initrd/Library/bsh
mkdir -p $(BUILD_DIR)/initrd/Library/BWM/Wallpaper
mkdir -p $(BUILD_DIR)/initrd/Library/art
mkdir -p $(BUILD_DIR)/initrd/Library/images/branding
mkdir -p $(BUILD_DIR)/initrd/docs mkdir -p $(BUILD_DIR)/initrd/docs
mkdir -p $(BUILD_DIR)/initrd/boot
mkdir -p $(BUILD_DIR)/initrd/mnt
mkdir -p $(BUILD_DIR)/initrd/dev
mkdir -p $(BUILD_DIR)/initrd/root/Desktop
mkdir -p $(BUILD_DIR)/initrd/root/Pictures
mkdir -p $(BUILD_DIR)/initrd/root/Documents
mkdir -p $(BUILD_DIR)/initrd/root/Downloads
mkdir -p $(BUILD_DIR)/initrd/etc
mkdir -p $(BUILD_DIR)/initrd/usr/lib/tcc/include
mkdir -p $(BUILD_DIR)/initrd/usr/local/include
mkdir -p $(BUILD_DIR)/initrd/usr/include/sys
mkdir -p $(BUILD_DIR)/initrd/usr/include/libc
mkdir -p $(BUILD_DIR)/initrd/usr/lib
@printf "$(YELLOW)[COPY]$(RESET) Userland binaries..." @printf "$(YELLOW)[COPY]$(RESET) Limine binaries + kernel for installer...\n"
@if [ -f limine/BOOTX64.EFI ]; then cp limine/BOOTX64.EFI $(BUILD_DIR)/initrd/boot/; fi
@if [ -f limine/BOOTIA32.EFI ]; then cp limine/BOOTIA32.EFI $(BUILD_DIR)/initrd/boot/; fi
@if [ -f limine/limine-bios.sys ]; then cp limine/limine-bios.sys $(BUILD_DIR)/initrd/boot/; fi
@cp $(KERNEL_ELF) $(BUILD_DIR)/initrd/boot/boredos.elf
@printf "$(YELLOW)[COPY]$(RESET) Userland binaries...\n"
@for f in $(SRC_DIR)/userland/bin/*.elf; do \ @for f in $(SRC_DIR)/userland/bin/*.elf; do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/bin/; \ cp "$$f" $(BUILD_DIR)/initrd/bin/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) Wallpapers..." @printf "$(YELLOW)[COPY]$(RESET) TCC support files...\n"
@cp $(SRC_DIR)/userland/cli/third_party/tcc/libtcc1.a $(BUILD_DIR)/initrd/usr/lib/tcc/
@cp $(SRC_DIR)/userland/cli/third_party/tcc/libtcc1.a $(BUILD_DIR)/initrd/usr/lib/
@cp $(SRC_DIR)/userland/cli/third_party/tcc/include/*.h $(BUILD_DIR)/initrd/usr/lib/tcc/include/
@cp $(SRC_DIR)/userland/sdk/lib/libboredos.a $(BUILD_DIR)/initrd/usr/lib/
@cp $(SRC_DIR)/userland/sdk/lib/libc.a $(BUILD_DIR)/initrd/usr/lib/
@cp $(SRC_DIR)/userland/sdk/lib/libm.a $(BUILD_DIR)/initrd/usr/lib/
@cp $(SRC_DIR)/userland/bin/crt0.o $(BUILD_DIR)/initrd/usr/lib/crt0.o
@cp $(SRC_DIR)/userland/bin/crt0.o $(BUILD_DIR)/initrd/usr/lib/crt1.o
@cp $(SRC_DIR)/userland/bin/empty.o $(BUILD_DIR)/initrd/usr/lib/crti.o
@cp $(SRC_DIR)/userland/bin/empty.o $(BUILD_DIR)/initrd/usr/lib/crtn.o
@cp $(SRC_DIR)/userland/libc/*.h $(BUILD_DIR)/initrd/usr/include/
@cp $(SRC_DIR)/userland/libc/sys/*.h $(BUILD_DIR)/initrd/usr/include/sys/
@cp $(SRC_DIR)/userland/libc/*.h $(BUILD_DIR)/initrd/usr/include/libc/
@cp $(SRC_DIR)/userland/libc/*.h $(BUILD_DIR)/initrd/usr/local/include/
@cp $(SRC_DIR)/userland/stb_image.h $(BUILD_DIR)/initrd/usr/include/
@printf "$(YELLOW)[COPY]$(RESET) Wallpapers...\n"
@for f in $(SRC_DIR)/images/wallpapers/*; do \ @for f in $(SRC_DIR)/images/wallpapers/*; do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; \ cp "$$f" $(BUILD_DIR)/initrd/Library/images/Wallpapers/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) GIF assets..." @printf "$(YELLOW)[COPY]$(RESET) GIF assets...\n"
@for f in $(SRC_DIR)/images/gif/*.gif; do \ @for f in $(SRC_DIR)/images/gif/*.gif; do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; \ cp "$$f" $(BUILD_DIR)/initrd/Library/images/gif/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) Colloid icons..." @printf "$(YELLOW)[COPY]$(RESET) Colloid icons...\n"
@for f in $(COLLOID_ICONS); do \ @for f in $(COLLOID_ICONS); do \
src="$(SRC_DIR)/images/icons/colloid/$$f"; \ src="$(SRC_DIR)/images/icons/colloid/$$f"; \
if [ -f "$$src" ]; then \ if [ -f "$$src" ]; then \
printf " -> $$src"; \ printf " -> $$src\n"; \
cp "$$src" $(BUILD_DIR)/initrd/Library/images/icons/colloid/; \ cp "$$src" $(BUILD_DIR)/initrd/Library/images/icons/colloid/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) Fonts..." @printf "$(YELLOW)[COPY]$(RESET) BoredOS icons...\n"
@mkdir -p $(BUILD_DIR)/initrd/Library/images/icons/boredos
@for f in $(SRC_DIR)/images/icons/boredos/*.png; do \
if [ -f "$$f" ]; then \
printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/Library/images/icons/boredos/; \
fi \
done
@printf "$(YELLOW)[COPY]$(RESET) Branding assets...\n"
@cp -r branding/* $(BUILD_DIR)/initrd/Library/images/branding/
@printf "$(YELLOW)[COPY]$(RESET) Fonts...\n"
@for f in $(SRC_DIR)/fonts/*.ttf; do \ @for f in $(SRC_DIR)/fonts/*.ttf; do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; \ cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) Emoji fonts..." @printf "$(YELLOW)[COPY]$(RESET) Emoji fonts...\n"
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \ @for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; \ cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) bsh configuration..." @printf "$(YELLOW)[COPY]$(RESET) bsh configuration...\n"
@if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then printf " -> bshrc"; cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi @if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then printf " -> bshrc\n"; cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then printf " -> startup.bsh"; cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi @if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then printf " -> startup.bsh\n"; cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then printf " -> boot.bsh"; cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi @if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then printf " -> boot.bsh\n"; cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/library/conf/sysfetch.cfg ]; then printf " -> sysfetch.cfg\n"; cp $(SRC_DIR)/library/conf/sysfetch.cfg $(BUILD_DIR)/initrd/Library/conf/; fi
@printf "$(YELLOW)[COPY]$(RESET) DOOM assets..." @printf "$(YELLOW)[COPY]$(RESET) DOOM assets...\n"
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then printf " -> doom1.wad"; cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi @if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then printf " -> doom1.wad\n"; cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
@printf "$(YELLOW)[COPY]$(RESET) Documentation..." @printf "$(YELLOW)[COPY]$(RESET) ASCII art...\n"
@if [ -f $(SRC_DIR)/library/art/boredos.txt ]; then printf " -> boredos.txt\n"; cp $(SRC_DIR)/library/art/boredos.txt $(BUILD_DIR)/initrd/Library/art/; fi
@printf "$(YELLOW)[COPY]$(RESET) Documentation...\n"
@for f in $$(find docs -name '*.md' 2>/dev/null); do \ @for f in $$(find docs -name '*.md' 2>/dev/null); do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \
printf " -> $$f"; \ printf " -> $$f\n"; \
dir=$$(dirname "$$f"); \ dir=$$(dirname "$$f"); \
mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \ mkdir -p $(BUILD_DIR)/initrd/"$$dir"; \
cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \ cp "$$f" $(BUILD_DIR)/initrd/"$$dir"/; \
fi \ fi \
done done
@printf "$(YELLOW)[COPY]$(RESET) Root files..." @printf "$(YELLOW)[COPY]$(RESET) Root files...\n"
@if [ -f README.md ]; then printf " -> README.md"; cp README.md $(BUILD_DIR)/initrd/; fi @if [ -f README.md ]; then printf " -> README.md\n"; cp README.md $(BUILD_DIR)/initrd/; fi
@if [ -f LICENSE ]; then printf " -> LICENSE"; cp LICENSE $(BUILD_DIR)/initrd/; fi @if [ -f LICENSE ]; then printf " -> LICENSE\n"; cp LICENSE $(BUILD_DIR)/initrd/; fi
@if [ -f limine.conf ]; then printf " -> limine.conf"; cp limine.conf $(BUILD_DIR)/initrd/; fi @if [ -f limine.conf ]; then printf " -> limine.conf\n"; cp limine.conf $(BUILD_DIR)/initrd/; fi
@printf "$(YELLOW)[TAR]$(RESET) Creating initrd.tar..." @printf "$(YELLOW)[TAR]$(RESET) Creating initrd.tar...\n"
cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar * cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar *
@printf "$(GREEN)[OK]$(RESET) Initrd created: $(BUILD_DIR)/initrd.tar" @printf "$(GREEN)[OK]$(RESET) Initrd created: $(BUILD_DIR)/initrd.tar\n"
$(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup $(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
$(call PRINT_STEP,CREATING ISO IMAGE) $(call PRINT_STEP,CREATING ISO IMAGE)
@printf "$(YELLOW)[ISO]$(RESET) Cleaning previous ISO root..." @printf "$(YELLOW)[ISO]$(RESET) Cleaning previous ISO root...\n"
rm -rf $(ISO_DIR) rm -rf $(ISO_DIR)
@printf "$(YELLOW)[ISO]$(RESET) Creating ISO directory structure..." @printf "$(YELLOW)[ISO]$(RESET) Creating ISO directory structure...\n"
mkdir -p $(ISO_DIR) mkdir -p $(ISO_DIR)
mkdir -p $(ISO_DIR)/EFI/BOOT mkdir -p $(ISO_DIR)/EFI/BOOT
@printf "$(YELLOW)[COPY]$(RESET) Kernel ELF..." @printf "$(YELLOW)[COPY]$(RESET) Kernel ELF...\n"
cp $(KERNEL_ELF) $(ISO_DIR)/ cp $(KERNEL_ELF) $(ISO_DIR)/
@printf "$(YELLOW)[COPY]$(RESET) Limine config..." @printf "$(YELLOW)[COPY]$(RESET) Limine config...\n"
cp limine.conf $(ISO_DIR)/ cp limine.conf $(ISO_DIR)/
@printf "$(YELLOW)[COPY]$(RESET) Initrd..." @printf "$(YELLOW)[COPY]$(RESET) Initrd...\n"
cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/ cp $(BUILD_DIR)/initrd.tar $(ISO_DIR)/
@printf "$(YELLOW)[CONFIG]$(RESET) Adding initrd module path..." @printf "$(YELLOW)[CONFIG]$(RESET) Adding initrd module path...\n"
printf " module_path: boot():/initrd.tar" >> $(ISO_DIR)/limine.conf printf " module_path: boot():/initrd.tar\n" >> $(ISO_DIR)/limine.conf
@printf "$(YELLOW)[COPY]$(RESET) Optional splash image..." @printf "$(YELLOW)[COPY]$(RESET) Optional splash image...\n"
@if [ -f splash.jpg ]; then printf " -> splash.jpg"; cp splash.jpg $(ISO_DIR)/; else printf " -> no splash.jpg found"; fi @if [ -f branding/splash.jpg ]; then printf " -> splash.jpg\n"; cp branding/splash.jpg $(ISO_DIR)/splash.jpg; else printf " -> no splash.jpg found\n"; fi
@printf "$(YELLOW)[COPY]$(RESET) Limine boot files..." @printf "$(YELLOW)[COPY]$(RESET) Limine boot files...\n"
cp limine/limine-bios.sys $(ISO_DIR)/ cp limine/limine-bios.sys $(ISO_DIR)/
cp limine/limine-bios-cd.bin $(ISO_DIR)/ cp limine/limine-bios-cd.bin $(ISO_DIR)/
cp limine/limine-uefi-cd.bin $(ISO_DIR)/ cp limine/limine-uefi-cd.bin $(ISO_DIR)/
@printf "$(YELLOW)[COPY]$(RESET) EFI bootloaders..." @printf "$(YELLOW)[COPY]$(RESET) EFI bootloaders...\n"
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/ cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/ cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
@ -321,40 +301,122 @@ $(ISO_IMAGE): $(KERNEL_ELF) $(BUILD_DIR)/initrd.tar limine.conf limine-setup
-efi-boot-part --efi-boot-image --protective-msdos-label \ -efi-boot-part --efi-boot-image --protective-msdos-label \
$(ISO_DIR) -o $(ISO_IMAGE) $(ISO_DIR) -o $(ISO_IMAGE)
@printf "$(YELLOW)[LIMINE]$(RESET) Installing BIOS bootloader..." @printf "$(YELLOW)[LIMINE]$(RESET) Installing BIOS bootloader...\n"
./limine/limine bios-install $(ISO_IMAGE) ./limine/limine bios-install $(ISO_IMAGE)
@printf "$(GREEN)[OK]$(RESET) ISO image ready: $(ISO_IMAGE)" @printf "$(GREEN)[OK]$(RESET) ISO image ready: $(ISO_IMAGE)\n"
clean: clean:
$(call PRINT_STEP,CLEANING BUILD OUTPUT) $(call PRINT_STEP,CLEANING BUILD OUTPUT)
rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE) rm -rf $(BUILD_DIR) $(ISO_DIR) $(ISO_IMAGE)
$(MAKE) -C $(SRC_DIR)/userland clean $(MAKE) -C $(SRC_DIR)/userland clean
@printf "$(GREEN)[OK]$(RESET) Clean complete." @printf "$(GREEN)[OK]$(RESET) Clean complete.\n"
run-windows: $(ISO_IMAGE) disk.qcow2:
$(call PRINT_STEP,CREATING 10GB EXPANDABLE DISK IMAGE)
qemu-img create -f qcow2 disk.qcow2 10G
run: $(ISO_IMAGE) disk.qcow2
$(call PRINT_STEP,DETECTING PLATFORM AND RUNNING BOREDOS)
@if [ "$(HOST_OS)" = "Darwin" ]; then \
printf "$(GREEN)[RUN]$(RESET) Detected macOS\n"; \
$(MAKE) run-mac; \
elif [ "$(HOST_OS)" = "Linux" ]; then \
printf "$(GREEN)[RUN]$(RESET) Detected Linux\n"; \
$(MAKE) run-linux; \
else \
printf "$(GREEN)[RUN]$(RESET) Detected Windows\n"; \
$(MAKE) run-windows; \
fi
run-hd: disk.qcow2 $(OVMF_VARS)
$(call PRINT_STEP,DETECTING PLATFORM AND BOOTING FROM HARD DRIVE)
@if [ "$(HOST_OS)" = "Darwin" ]; then \
printf "$(GREEN)[RUN-HD]$(RESET) Detected macOS\n"; \
$(MAKE) run-hd-mac; \
elif [ "$(HOST_OS)" = "Linux" ]; then \
printf "$(GREEN)[RUN-HD]$(RESET) Detected Linux\n"; \
$(MAKE) run-hd-linux; \
else \
printf "$(GREEN)[RUN-HD]$(RESET) Detected Windows\n"; \
$(MAKE) run-hd-windows; \
fi
run-windows: $(ISO_IMAGE) disk.qcow2
$(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON WINDOWS) $(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON WINDOWS)
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \ qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
-smp 4 \ -smp 4 \
-audiodev dsound,id=audio0 -machine pcspk-audiodev=audio0 \ -audiodev dsound,id=audio0 -machine pcspk-audiodev=audio0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \ -vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-drive file=disk.img,format=raw,file.locking=off -drive file=disk.qcow2,format=qcow2,file.locking=off
run-mac: $(ISO_IMAGE) run-mac: $(ISO_IMAGE) disk.qcow2
$(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON MACOS) $(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON MACOS)
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \ qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
-smp 4 \ -smp 4 \
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \ -audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \ -vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-display cocoa,show-cursor=off \ -display cocoa,show-cursor=off \
-drive file=disk.img,format=raw,file.locking=off \ -device ahci,id=ahci -drive file=disk.qcow2,format=qcow2,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 \
-cpu max -cpu max
run-linux: $(ISO_IMAGE) OVMF_CODE := /opt/homebrew/share/qemu/edk2-x86_64-code.fd
OVMF_VARS_TMPL := /opt/homebrew/share/qemu/edk2-i386-vars.fd
OVMF_VARS := edk2-vars.fd
ifeq ($(shell test -f $(OVMF_CODE) && echo 1),)
OVMF_CODE := /usr/local/share/qemu/edk2-x86_64-code.fd
OVMF_VARS_TMPL := /usr/local/share/qemu/edk2-i386-vars.fd
endif
$(OVMF_VARS):
@if [ -f $(OVMF_VARS_TMPL) ]; then \
printf "$(YELLOW)[UEFI]$(RESET) Creating local NVRAM vars...\n"; \
cp $(OVMF_VARS_TMPL) $(OVMF_VARS); \
fi
run-hd-mac: disk.qcow2 $(OVMF_VARS)
$(call PRINT_STEP,BOOTING BOREDOS FROM HARD DRIVE ON MACOS)
qemu-system-x86_64 -m 4G -serial stdio -boot c \
-smp 4 \
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-display cocoa,show-cursor=off \
-drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \
-drive if=pflash,format=raw,file=$(OVMF_VARS) \
-device ahci,id=ahci \
-drive file=disk.qcow2,format=qcow2,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 \
-drive file=disk.img,format=raw,if=none,id=disk1 -device ide-hd,bus=ahci.1,drive=disk1 \
-cpu max
run-linux: $(ISO_IMAGE) disk.qcow2
$(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON LINUX) $(call PRINT_STEP,RUNNING BOREDOS IN QEMU ON LINUX)
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \ qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
-smp 4 \ -smp 4 \
-audiodev pa,id=audio0 -machine pcspk-audiodev=audio0 \ -audiodev pa,id=audio0 -machine pcspk-audiodev=audio0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \ -vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-display gtk,show-cursor=off \ -display gtk,show-cursor=off \
-drive file=disk.img,format=raw,file.locking=off \ -device ahci,id=ahci -drive file=disk.qcow2,format=qcow2,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 \
-cpu max
run-hd-windows: disk.qcow2
$(call PRINT_STEP,BOOTING BOREDOS FROM HARD DRIVE ON WINDOWS)
qemu-system-x86_64 -m 4G -serial stdio -boot c \
-smp 4 \
-audiodev dsound,id=audio0 -machine pcspk-audiodev=audio0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-device ahci,id=ahci \
-drive file=disk.qcow2,format=qcow2,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 \
-cpu max
run-hd-linux: disk.qcow2 $(OVMF_VARS)
$(call PRINT_STEP,BOOTING BOREDOS FROM HARD DRIVE ON LINUX)
qemu-system-x86_64 -m 4G -serial stdio -boot c \
-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 if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=$(OVMF_VARS) \
-device ahci,id=ahci \
-drive file=disk.qcow2,format=qcow2,if=none,id=disk0 -device ide-hd,bus=ahci.0,drive=disk0 \
-cpu max -cpu max

View file

@ -1,93 +1,99 @@
# BoredOS
<div align="center"> <div align="center">
<img src="boredos.svg" alt="BoredOS Logo" width="450" /> <img src="branding/bOS_full_gradient_cropped.png" alt="BoredOS Logo" width="450" />
<p><em>A modern x86_64 hobbyist operating system built from the ground up.</em></p>
<h3>A modern x86_64 hobbyist operating system built from the ground up.</h3>
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
![Platform: x86_64](https://img.shields.io/badge/Platform-x86_64-lightgrey) ![Platform: x86_64](https://img.shields.io/badge/Platform-x86_64-lightgrey)
![Status: Active](https://img.shields.io/badge/Status-Active-brightgreen) ![Status: Active](https://img.shields.io/badge/Status-Active-brightgreen)
![GitHub all releases](https://img.shields.io/github/downloads/boreddevnl/BoredOS/total?color=brightgreen) ![GitHub all releases](https://img.shields.io/github/downloads/boreddevnl/BoredOS/total?color=brightgreen)
<br />
[Docs](docs/README.md) · [Build & Run](docs/build/usage.md) · [AppDev SDK](docs/appdev/sdk_reference.md) · [Discord](https://discord.gg/J2BxWaFAgY) · [Support](https://buymeacoffee.com/boreddevhq)
</div> </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. ![Screenshot](branding/screenshot.jpg)
![Screenshot](screenshot.jpg)
> [!NOTE] > [!NOTE]
> *The screenshot above may represent a previous build and is subject to change as the UI evolves.* > The screenshot above may represent a previous build and is subject to change as the UI evolves.
--- ---
## Features ## Features
### System Architecture ### Kernel and Architecture
* **64-bit Long Mode:** Fully utilizing the x86_64 architecture. - **Long Mode Architecture** — Native x86_64 implementation utilizing 64-bit address space and registers
* **Symmetric Multi-Processing (SMP):** Full support for multi-core CPUs via Limine SMP. - **Symmetric Multi-Processing** — Scalable multi-core support with IPI-based scheduling and synchronization
* **LAPIC & IPI Scheduling:** Advanced interrupt handling and inter-processor communication for task distribution. - **Advanced Memory Management** — Custom slab allocator with object pooling and efficient physical/virtual page mapping
* **SMP-Safe Spinlocks:** Robust kernel-wide synchronization for VFS, process management, and the GUI. - **Hybrid VFS Layer** — Unified filesystem interface supporting FAT32, TAR, ProcFS, and SysFS
* **Multiboot2 Compliant:** Bootable on real hardware and modern emulators. - **Preemptive Multitasking** — Prioritized process scheduling with full context isolation
* **Kernel Core:** Interrupt Descriptor Table (IDT) management and a robust syscall interface. - **Hardware Abstraction** — Comprehensive driver support for PCI, AHCI, PS/2, and ACPI
* **Filesystem:** Full **FAT32** support for persistent and in-memory storage.
* **Networking:** Includes the lwIP networking stack and a basic web browser.
### Graphical User Interface ### Graphical Desktop Environment
* **BoredWM:** A custom Window Manager with drag-and-drop, mouse-centered interaction. - **BoredWM** — High-performance window manager featuring window stacking, focus management, and drag-and-drop interactions
* **Customization:** Adjustable UI to suit your aesthetic. - **Typography Engine** — Integrated font manager with TrueType (TTF) support and efficient glyph caching
* **Media Support:** Built-in image decoding. (PNG, GIF, JPEG, TGA, BMP) - **Rich Media Subsystem** — Native hardware-independent decoding for PNG, JPEG, GIF, BMP, and TGA formats
- **LibWidget Toolkit** — Native UI component library for rapid application development
### Networking Stack
- **TCP/IP Integration** — Full lwIP-based network stack featuring DHCP, DNS, and Berkeley-style sockets
- **Network Services** — Integrated support for basic web browsing and real-time network telemetry
### Application Ecosystem
| Category | Applications |
|----------|--------------|
| Productivity | Text Editor, Markdown Viewer, BoredWord Processor, Web Browser, Calculator |
| Development | TCC (Tiny C Compiler), Lua|
| System | Explorer (File Manager), Task Manager, System Monitor, Graphing Utility |
| Games | doomgeneric, Minesweeper, 2048, Snake |
### 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 ## 📚 Documentation
Explore the internal workings of BoredOS via our comprehensive guides in the [`docs/`](docs/) directory. | Guide | Description |
|-------|-------------|
* 📖 **[Documentation Index](docs/README.md)** Start here. | [Documentation Index](docs/README.md) | Start here! |
* 🏗️ **[Architecture Overview](docs/architecture/core.md)** Deep dive into the kernel. | [Architecture Overview](docs/architecture/README.md) | Deep dive into the kernel |
* 🔨 **[Building and Running](docs/build/usage.md)** Setup your build environment. | [Building and Running](docs/build/usage.md) | Set up your build environment |
* 🚀 **[AppDev SDK](docs/appdev/custom_apps.md)** Build your own apps for BoredOS. | [AppDev SDK](docs/appdev/custom_apps.md) | Build your own apps for BoredOS |
--- ---
## Support the Journey
If you find this project interesting or helpful, consider fueling the development with a coffee! ## ☕ Support the Journey
If you find BoredOS interesting or useful, consider fueling development with a coffee!
<a href="https://buymeacoffee.com/boreddevhq" 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;" /> <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" style="border-radius: 8px;" />
</a> </a>
--- ---
## Project Disclaimer & Heritage ## History
**BoredOS** is the successor to **[BrewKernel](https://github.com/boreddevnl/brewkernel)**, a project initiated in 2023. **BoredOS** is the successor to **[BrewKernel](https://github.com/boreddevnl/brewkernel)**, a project started in 2023. BrewKernel served as the foundational learning ground but has since been officially deprecated and archived — it no longer receives updates, bug fixes, or pull request reviews.
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. BoredOS is a complete architectural reboot, applying years of lessons learned to build a cleaner, more modular, and more capable system.
> [!IMPORTANT] > [!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. > Please direct all issues, discussions, and contributions 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 ## License
**Copyright (C) 2023-2026 boreddevnl** **Copyright (C) 20232026 boreddevnl**
Distributed under the **GNU General Public License v3**. See the `LICENSE` file for details. Distributed under the **GNU General Public License v3**. See [`LICENSE`](LICENSE) for details.
> [!IMPORTANT] > [!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. > You must retain all copyright headers and include the original attribution in any redistributions or derivative works. See the [`NOTICE`](NOTICE) file for more details.

BIN
branding/bOS10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
branding/bOS11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
branding/bOS12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
branding/bOS13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
branding/bOS14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
branding/bOS9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
branding/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
branding/screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

BIN
branding/splash.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
disk.img

Binary file not shown.

View file

@ -58,6 +58,7 @@ The SDK and toolchain guides for creating your own `.elf` userland binaries.
- [`ELF App Metadata`](appdev/elf_metadata.md): How to declare app icons and descriptions using source annotations, how the build system embeds them into `.note.boredos.app` ELF sections, and how the kernel reads them at runtime. - [`ELF App Metadata`](appdev/elf_metadata.md): How to declare app icons and descriptions using source annotations, how the build system embeds them into `.note.boredos.app` ELF sections, and how the kernel reads them at runtime.
- [`Example Apps`](appdev/examples/README.md): A collection of sample C applications ranging from basic terminal output to advanced TCP networking. - [`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. - [`Grapher`](appdev/grapher.md): Full reference for the built-in mathematical graphing application — equation syntax, keyboard controls, architecture, and configuration.
- [`Native TCC`](appdev/tcc.md): How to use the Tiny C Compiler (TCC) to build and run C applications directly on BoredOS.
### 4. [Usage](usage/) ### 4. [Usage](usage/)
General guides on how to interact with the OS. General guides on how to interact with the OS.

View file

@ -11,7 +11,7 @@ This example demonstrates how to create an empty window that stays active on the
* Including `libui.h` and the event structure. * Including `libui.h` and the event structure.
* Creating a `ui_window_t` handle. * Creating a `ui_window_t` handle.
* Creating an infinite event loop using `ui_get_event()`. * Creating an infinite event loop using `ui_get_event()`.
* Yielding CPU time to the kernel via `sys_yield()`. * Yielding CPU time via `sleep(ms)`.
* Declaring app metadata via source annotations. * Declaring app metadata via source annotations.
--- ---
@ -50,10 +50,11 @@ int main(void) {
} }
} }
// 4. CRITICAL: Yield the remainder of our timeslice // 4. CRITICAL: Throttle our loop to save CPU
// If we don't do this, the while(1) loop will consume 100% of the CPU // If we don't do this, the while(1) loop will consume 100% of the CPU
// and starve the rest of the OS! // and starve the rest of the OS! A 10ms sleep allows for ~100 FPS
sys_yield(); // event polling while letting the CPU actually idle.
sys_system(SYSTEM_CMD_SLEEP, 10, 0, 0, 0);
} }
// Returning from main will automatically destroy the window and exit the process. // Returning from main will automatically destroy the window and exit the process.
@ -66,7 +67,7 @@ int main(void) {
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. 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. 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. 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." 4. **CPU Throttling**: Since we are constantly polling in a loop, we call `sys_system(SYSTEM_CMD_SLEEP, 10, ...)` at the end of the loop frame. This tells the OS scheduler, "I'm done checking for events, don't run me again for at least 10ms." This allows the CPU to actually enter a low-power state and makes the system much smoother.
5. **`BOREDOS_APP_DESC` / `BOREDOS_APP_ICONS`**: Embedded into the `.elf` by the build system as a BoredOS NOTE section. The Window Manager reads this at runtime to render the app's icon on the Desktop and in the File Explorer. See [`elf_metadata.md`](../elf_metadata.md) for full details. 5. **`BOREDOS_APP_DESC` / `BOREDOS_APP_ICONS`**: Embedded into the `.elf` by the build system as a BoredOS NOTE section. The Window Manager reads this at runtime to render the app's icon on the Desktop and in the File Explorer. See [`elf_metadata.md`](../elf_metadata.md) for full details.
## Running It ## Running It

View file

@ -76,8 +76,8 @@ int main(void) {
// Step D: Instruct the compositor to flush our drawing buffer to the physical screen // Step D: Instruct the compositor to flush our drawing buffer to the physical screen
ui_mark_dirty(wid, 0, 0, W_WIDTH, W_HEIGHT); ui_mark_dirty(wid, 0, 0, W_WIDTH, W_HEIGHT);
// 4. Yield and throttle // 4. Yield and throttle (targeting ~60 FPS)
sys_yield(); sys_system(SYSTEM_CMD_SLEEP, 16, 0, 0, 0);
} }
return 0; return 0;
@ -93,4 +93,4 @@ int main(void) {
5. **`BOREDOS_APP_DESC` / `BOREDOS_APP_ICONS`**: Embedded into the compiled `.elf` as a BoredOS NOTE section. The Desktop and File Explorer read this to show the game's icon instead of the generic binary icon. See [`elf_metadata.md`](../elf_metadata.md) for full details. 5. **`BOREDOS_APP_DESC` / `BOREDOS_APP_ICONS`**: Embedded into the compiled `.elf` as a BoredOS NOTE section. The Desktop and File Explorer read this to show the game's icon instead of the generic binary icon. See [`elf_metadata.md`](../elf_metadata.md) for full details.
> [!WARNING] > [!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. > Because `sys_system(SYSTEM_CMD_SLEEP, ...)`'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.

View file

@ -24,6 +24,7 @@ Primary headers are in `src/userland/libc/` and UI helpers are in `src/wm/`.
- [`Syscalls`](syscalls.md): syscall numbers, FS/SYSTEM command IDs, and wrappers - [`Syscalls`](syscalls.md): syscall numbers, FS/SYSTEM command IDs, and wrappers
- [`UI API`](ui_api.md): drawing and event APIs - [`UI API`](ui_api.md): drawing and event APIs
- [`Widget API`](widget_api.md): common widgets and interaction helpers - [`Widget API`](widget_api.md): common widgets and interaction helpers
- [`Native TCC`](tcc.md): Native C compilation directly on BoredOS
## Typical Include Set ## Typical Include Set

View file

@ -15,6 +15,7 @@ Use libc wrappers when possible instead of calling raw syscall numbers directly.
| 3 | `SYS_GUI` | Window manager and drawing commands | | 3 | `SYS_GUI` | Window manager and drawing commands |
| 4 | `SYS_FS` | Filesystem and fd commands | | 4 | `SYS_FS` | Filesystem and fd commands |
| 5 | `SYS_SYSTEM` | System-wide command multiplexer | | 5 | `SYS_SYSTEM` | System-wide command multiplexer |
| 8 | `SYS_DEBUG_SERIAL` | Debug serial output (kernel only) |
| 9 | `SYS_SBRK` (userland header) | Heap break management | | 9 | `SYS_SBRK` (userland header) | Heap break management |
| 10 | `SYS_KILL` (userland header) | Kill process by PID | | 10 | `SYS_KILL` (userland header) | Kill process by PID |
| 60 | `SYS_EXIT` (kernel header) | Internal kernel syscall number map | | 60 | `SYS_EXIT` (kernel header) | Internal kernel syscall number map |
@ -46,6 +47,32 @@ Notes:
| 17 | `FS_CMD_PIPE` | Create pipe | | 17 | `FS_CMD_PIPE` | Create pipe |
| 18 | `FS_CMD_FCNTL` | `fcntl` flags ops | | 18 | `FS_CMD_FCNTL` | `fcntl` flags ops |
## GUI Command IDs (`SYS_GUI`)
| ID | Macro | Meaning |
|---|---|---|
| 1 | `GUI_CMD_WINDOW_CREATE` | Create a new window |
| 2 | `GUI_CMD_DRAW_RECT` | Draw a rectangle |
| 3 | `GUI_CMD_DRAW_STRING` | Draw a string (TTF) |
| 4 | `GUI_CMD_MARK_DIRTY` | Mark region dirty / dual-buffer commit |
| 5 | `GUI_CMD_GET_EVENT` | Retrieve next GUI event |
| 6 | `GUI_CMD_DRAW_ROUNDED_RECT_FILLED` | Draw filled rounded rectangle |
| 7 | `GUI_CMD_DRAW_IMAGE` | Draw raw image data |
| 8 | `GUI_CMD_GET_STRING_WIDTH` | Get TTF string width |
| 9 | `GUI_CMD_GET_FONT_HEIGHT` | Get TTF font height |
| 10 | `GUI_CMD_DRAW_STRING_BITMAP` | Draw legacy bitmap string |
| 11 | `GUI_CMD_DRAW_STRING_SCALED` | Draw scaled TTF string |
| 12 | `GUI_CMD_GET_STRING_WIDTH_SCALED` | Get scaled TTF string width |
| 13 | `GUI_CMD_GET_FONT_HEIGHT_SCALED` | Get scaled TTF font height |
| 14 | `GUI_CMD_WINDOW_SET_RESIZABLE` | Toggle window resizability |
| 15 | `GUI_CMD_WINDOW_SET_TITLE` | Update window title |
| 16 | `GUI_CMD_SET_FONT` | Load/set window-specific font |
| 18 | `GUI_CMD_DRAW_STRING_SCALED_SLOPED` | Draw sloped/scaled TTF string |
| 50 | `GUI_CMD_GET_SCREEN_SIZE` | Get display resolution |
| 51 | `GUI_CMD_GET_SCREENBUFFER` | Copy screen contents to buffer |
| 52 | `GUI_CMD_SHOW_NOTIFICATION` | Show desktop notification |
| 53 | `GUI_CMD_GET_DATETIME` | Get RTC datetime |
## SYSTEM Command IDs (`SYS_SYSTEM`) ## SYSTEM Command IDs (`SYS_SYSTEM`)
### Desktop and display ### Desktop and display
@ -65,6 +92,10 @@ Notes:
| 31 | `SYSTEM_CMD_SET_WALLPAPER_PATH` | Set wallpaper from path | | 31 | `SYSTEM_CMD_SET_WALLPAPER_PATH` | Set wallpaper from path |
| 40 | `SYSTEM_CMD_SET_FONT` | Set active font | | 40 | `SYSTEM_CMD_SET_FONT` | Set active font |
| 47 | `SYSTEM_CMD_SET_RESOLUTION` | Set display mode | | 47 | `SYSTEM_CMD_SET_RESOLUTION` | Set display mode |
| 49 | `SYSTEM_CMD_SET_KEYBOARD_LAYOUT` | Set active keyboard layout ID |
| 51 | `SYSTEM_CMD_GET_KEYBOARD_LAYOUT` | Get current keyboard layout ID |
| 52 | `SYSTEM_GET_CURSOR_SCALE` | Get the current BoredWM cursor scale |
| 53 | `SYSTEM_SET_CURSOR_SCALE` | Set the BoredWM cursor scale |
### Time, power, and system state ### Time, power, and system state
@ -79,8 +110,8 @@ Notes:
| 28 | `SYSTEM_CMD_GET_SHELL_CONFIG` | Read shell config value | | 28 | `SYSTEM_CMD_GET_SHELL_CONFIG` | Read shell config value |
| 32 | `SYSTEM_CMD_RTC_SET` | Set RTC datetime | | 32 | `SYSTEM_CMD_RTC_SET` | Set RTC datetime |
| 41 | `SYSTEM_CMD_SET_RAW_MODE` | Terminal raw-mode control | | 41 | `SYSTEM_CMD_SET_RAW_MODE` | Terminal raw-mode control |
| 43 | `SYSTEM_CMD_YIELD` | Yield scheduler timeslice | | 43 | `SYSTEM_CMD_YIELD` | Yield scheduler timeslice (Not recommended for idle loops) |
| 46 | `SYSTEM_CMD_SLEEP` | Sleep current process | | 46 | `SYSTEM_CMD_SLEEP` | Sleep current process (Recommended for throttling) |
### Network ### Network
@ -138,10 +169,25 @@ Notes:
| 76 | `SYSTEM_CMD_GET_ELF_METADATA` | Read full app metadata from an ELF | | 76 | `SYSTEM_CMD_GET_ELF_METADATA` | Read full app metadata from an ELF |
| 77 | `SYSTEM_CMD_GET_ELF_PRIMARY_IMAGE` | Read primary icon path from an ELF | | 77 | `SYSTEM_CMD_GET_ELF_PRIMARY_IMAGE` | Read primary icon path from an ELF |
### Disk Management
| ID | Macro | Meaning |
|---|---|---|
| 100 | `SYSTEM_CMD_DISK_GET_COUNT` | Get number of detected disks |
| 101 | `SYSTEM_CMD_DISK_GET_INFO` | Get metadata for a specific disk/partition |
| 102 | `SYSTEM_CMD_DISK_WRITE_GPT` | Write GPT partition table to disk |
| 103 | `SYSTEM_CMD_DISK_WRITE_MBR` | Write MBR partition table to disk |
| 104 | `SYSTEM_CMD_DISK_MKFS_FAT32` | Format a partition as FAT32 |
| 105 | `SYSTEM_CMD_DISK_MOUNT` | Mount a filesystem |
| 106 | `SYSTEM_CMD_DISK_UMOUNT` | Unmount a filesystem |
| 107 | `SYSTEM_CMD_DISK_RESCAN` | Rescan disk for partition changes |
| 108 | `SYSTEM_CMD_DISK_REPLACE_KERNEL` | Copy new kernel to ESP / boot partition |
| 109 | `SYSTEM_CMD_DISK_SYNC` | Flush disk caches for a mountpoint |
## Common Wrapper API (`src/userland/libc/syscall.h`) ## Common Wrapper API (`src/userland/libc/syscall.h`)
Typical wrappers used by apps: Typical wrappers used by apps:
- Process/system: `sys_exit`, `sys_yield`, `sys_spawn`, `sys_exec`, `sys_waitpid`, `sys_kill_signal` - Process/system: `sys_exit`, `sys_yield`, `sys_system` (with `SYSTEM_CMD_SLEEP`), `sys_spawn`, `sys_exec`, `sys_waitpid`
- Filesystem: `sys_open`, `sys_read`, `sys_write_fs`, `sys_close`, `sys_seek`, `sys_tell`, `sys_size`, `sys_list` - Filesystem: `sys_open`, `sys_read`, `sys_write_fs`, `sys_close`, `sys_seek`, `sys_tell`, `sys_size`, `sys_list`
- Network: `sys_network_init`, `sys_network_dhcp_acquire`, `sys_udp_send`, `sys_tcp_connect`, `sys_tcp_recv_nb`, `sys_dns_lookup` - Network: `sys_network_init`, `sys_network_dhcp_acquire`, `sys_udp_send`, `sys_tcp_connect`, `sys_tcp_recv_nb`, `sys_dns_lookup`
- TTY: `sys_tty_create`, `sys_tty_read_out`, `sys_tty_write_in`, `sys_tty_set_fg` - TTY: `sys_tty_create`, `sys_tty_read_out`, `sys_tty_write_in`, `sys_tty_set_fg`

99
docs/appdev/tcc.md Normal file
View file

@ -0,0 +1,99 @@
# Native Development with TCC
BoredOS includes a native port of the **Tiny C Compiler (TCC)**, allowing you to compile and run C programs directly within the operating system.
## Basic Usage
The compiler is available as `tcc`. You can use it much like you would on a standard Unix-like system.
### Compiling a Simple CLI Program
Create a file named `hello.c`:
```c
#include <stdio.h>
int main() {
printf("Hello from BoredOS native TCC!\n");
return 0;
}
```
Compile and run it:
```bash
tcc hello.c -o hello.elf
./hello.elf
```
## Developing GUI Applications
To develop applications that use the BoredOS Window Manager and UI library, you need to link against `libboredos`.
### Example GUI App (`hello_gui.c`)
```c
#include <libc/libui.h>
#include <libc/syscall.h>
int main() {
ui_window_t win = ui_window_create("Hello TCC", 100, 100, 300, 200);
if (!win) return 1;
gui_event_t ev;
while (1) {
if (ui_get_event(win, &ev)) {
if (ev.type == GUI_EVENT_PAINT) {
ui_draw_string(win, 20, 40, "Compiled natively!", 0xFFFFFFFF);
ui_mark_dirty(win, 0, 0, 300, 200);
} else if (ev.type == GUI_EVENT_CLOSE) {
break;
}
}
}
return 0;
}
```
### Compilation Command
```bash
tcc hello_gui.c -o hello_gui.elf -lboredos
```
> [!NOTE]
> The compiler automatically searches `/usr/include` for headers and `/usr/lib` for libraries. The BoredOS SDK headers and `libboredos.a` are pre-installed in these locations.
## Technical Details
### Standard Paths
- **Headers**: `/usr/include`, `/usr/local/include`
- **Libraries**: `/usr/lib`
- **TCC Internal**: `/usr/lib/tcc`
### Compilation Process
BoredOS TCC generates standard **ELF64** binaries. It automatically links with:
1. **`crt0.o`**: Entry point initialization.
2. **`crti.o` / `crtn.o`**: Constructor/Destructor support.
3. **`libc.a`**: The BoredOS standard C library.
4. **`libtcc1.a`**: TCC runtime support.
### Memory & Storage Requirements
- **Static Linking Only**: BoredOS currently only supports static linking for native binaries.
- **Live ISO Mode**: You are limited by the 128MB RAMFS capacity. Compiling very large projects may fail if this limit is reached.
- **Disk Installation**: The compiler writes directly to your persistent disk. Your storage capacity is limited only by the size of your partition, and your work persists across reboots.
- **System RAM**: The kernel statically reserves 128MB for the internal RAMFS regardless of boot mode, though this does not limit your storage on a disk install.
- **No JIT**: The `tcc -run` feature is currently unsupported due to kernel memory protection and the lack of `mmap` with execution permissions in userland.
## Troubleshooting
### I/O Error during compilation
If you encounter an "I/O Error" while writing the output file, you may have run out of space.
- **Live ISO**: You have exceeded the 128MB RAMFS limit.
- **Disk Installation**: Your disk partition is full.
### Missing Headers
Ensure that you are including headers using the standard syntax: `#include <stdio.h>`. If you are using custom paths, use the `-I` flag:
```bash
tcc myapp.c -I/root/my_headers -o myapp.elf
```

View file

@ -68,9 +68,9 @@ Applications must continuously poll for events inside an infinite `$while(1)` lo
Returns `true` if an event was waiting in the queue, populating the `ev` structure. Returns `false` if the queue is empty. Returns `true` if an event was waiting in the queue, populating the `ev` structure. Returns `false` if the queue is empty.
> [!IMPORTANT] > [!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. > Because `ui_get_event` is non-blocking, you must call `sleep(ms);` or `sys_system(SYSTEM_CMD_SLEEP, ms, ...)` inside your event loop if no event was received.
> >
> All UI syscalls are **Thread-Safe** at the kernel level via the global GUI spinlock. > Historically, BoredOS used `sys_yield()`, but in the **Multi-Core (SMP)** architecture, yielding alone will still pin a CPU core to 100% usage. Using a short sleep (e.g., 5-10ms) ensures your app remains responsive while allowing the CPU to actually idle.
### Graphical Event Structure ### Graphical Event Structure

View file

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

View file

@ -0,0 +1,32 @@
# BoredOS Architecture
This folder gathers the architecture documentation that explains how BoredOS is built from the kernel up.
## Architecture roadmap
The documentation is split by area so you can go directly to the subsystem you want to understand.
| Area | Document | Description |
| --- | --- | --- |
| Graphics | [`graphics/window_manager.md`](architecture/graphics/window_manager.md) | Window manager design and display composition. |
| Hardware | [`hardware/input.md`](architecture/hardware/input.md) | Hardware-level input support and device wiring. |
| Hardware | [`hardware/pci.md`](architecture/hardware/pci.md) | PCI bus management and device enumeration. |
| Input | [`input/keyboard.md`](architecture/input/keyboard.md) | Keyboard input handling and key mapping. |
| Memory | [`memory/memory.md`](architecture/memory/memory.md) | Memory architecture, paging, and address space layout. |
| Memory | [`memory/memory_manager.md`](architecture/memory/memory_manager.md) | Memory allocation and management systems. |
| Network | [`network/network_stack.md`](architecture/network/network_stack.md) | TCP/IP stack design, protocol flow, and packet handling. |
| Network | [`network/network_drivers.md`](architecture/network/network_drivers.md) | Network driver architecture and interface support. |
| Storage | [`storage/filesystem.md`](architecture/storage/filesystem.md) | File system structure and storage access. |
| Storage | [`storage/ahci_drivers.md`](architecture/storage/ahci_drivers.md) | AHCI driver implementation and disk controller support. |
| System | [`system/core.md`](architecture/system/core.md) | Core kernel architecture and main subsystems. |
| System | [`system/interrupts.md`](architecture/system/interrupts.md) | Interrupt handling and low-level event dispatch. |
| System | [`system/processes.md`](architecture/system/processes.md) | Process management, scheduling, and execution model. |
| General | [`versioning.md`](architecture/versioning.md) | Release versioning and project numbering conventions. |
## Quick start
- **Read `system/core.md` first** for the kernel overview.
- Then explore the subsystem area you need: `memory/`, `network/`, `storage/`, `graphics/`, or `system/`.
- Use `versioning.md` to understand BoredOS version rules.
> Note: The links above point directly to the most important architecture documents in this folder.

View file

@ -42,6 +42,12 @@ With the introduction of Symmetric Multi-Processing (SMP), the Window Manager (W
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. 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. 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.
## Cursor Rendering
The cursor is drawn by BoredWM rather than by userland. Its shape is a small bitmap mask where transparent cells are skipped, white cells draw the outline, and black cells draw the filled body. The WM expands each source cell by the active cursor scale before writing pixels into the back buffer.
The current scale is exposed to userland through `SYSTEM_GET_CURSOR_SCALE` and can be changed with `SYSTEM_SET_CURSOR_SCALE`. Settings uses those commands for the mouse panel, while the WM clamps the requested scale and forces a redraw so the new cursor size appears immediately.
> [!IMPORTANT] > [!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. > 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.

View file

@ -2,13 +2,20 @@
BoredOS is built cross-compiled from a host system (such as macOS or Linux) to target the generic `x86_64-elf` platform. BoredOS is built cross-compiled from a host system (such as macOS or Linux) to target the generic `x86_64-elf` platform.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Building the Cross-Compiler on Linux](#building-the-cross-compiler-on-linux)
- [Installing the Toolchain on Windows](#installing-the-toolchain-on-windows)
## Prerequisites ## Prerequisites
To build BoredOS, you need the following tools: To build BoredOS, you need the following tools:
1. **x86_64 ELF GCC Cross-Compiler**: 1. **x86_64 ELF GCC Cross-Compiler**:
- `x86_64-elf-gcc`: The C compiler targeting the freestanding overarching ELF environment. - `x86_64-elf-gcc`: The C compiler targeting the freestanding overarching ELF environment.
- `x86_64-elf-ld`: The linker to combine object files into the final `boredos.elf` kernel binary and userland variables. - `x86_64-elf-ld`: The linker to combine object files into the final `boredos.elf` kernel and userland binaries.
2. **NASM**: 2. **NASM**:
- Required to compile the `.asm` files in `src/arch/` and `src/userland/crt0.asm`. It formats the output as `elf64` objects to be linked alongside the C code. - Required to compile the `.asm` files in `src/arch/` and `src/userland/crt0.asm`. It formats the output as `elf64` objects to be linked alongside the C code.
@ -19,3 +26,169 @@ To build BoredOS, you need the following tools:
4. **QEMU** (Optional but highly recommended for testing): 4. **QEMU** (Optional but highly recommended for testing):
- `qemu-system-x86_64` is used to virtualize the OS for testing or to mess around. - `qemu-system-x86_64` is used to virtualize the OS for testing or to mess around.
## Building the Cross-Compiler on Linux
### Availability Issue
On most Linux distributions, the `x86_64-elf-gcc` cross-compiler binary is **not pre-packaged** in standard repositories. The only notable exception is **Arch Linux** and Arch-based distributions (Manjaro, EndeavourOS, etc.), where it can be installed via `pacman`:
```bash
pacman -S x86_64-elf-gcc x86_64-elf-binutils
```
For all other Linux distributions (Debian, Ubuntu, Fedora, openSUSE, etc.), you **must build the cross-compiler from source**.
### Building from Source
To build the x86_64-ELF GCC cross-compiler:
1. **Download prerequisites**:
- GNU Binutils source
- GCC source
2. **Configure and build Binutils**:
```bash
../binutils-*/configure --target=x86_64-elf --prefix=/usr/local/cross
make && make install
```
3. **Configure and build GCC**:
```bash
../gcc-*/configure --target=x86_64-elf --prefix=/usr/local/cross \
--without-headers --enable-languages=c
make all-gcc && make install-gcc
```
4. **Add to PATH**:
```bash
export PATH="/usr/local/cross/bin:$PATH"
```
Verify the installation:
```bash
x86_64-elf-gcc --version
```
> **Note**: Building the cross-compiler can take 20-30 minutes depending on system performance. This is a one-time setup cost.
## Installing the Toolchain on Windows
### Recommended Environment: MSYS2
On Windows, the recommended way to build BoredOS is using **MSYS2**.
MSYS2 provides a Unix-like environment with the `pacman` package manager, making it easy to install the required development tools.
---
## 1. Install MSYS2
Download and install MSYS2 from the official website:
- https://www.msys2.org/
After installation, launch the **MSYS2 UCRT64** terminal.
---
## 2. Update MSYS2
Before installing packages, fully update the environment:
```bash
pacman -Syu
```
You may be asked to close the terminal after the first update.
If so:
1. Close the MSYS2 window
2. Reopen **MSYS2 UCRT64**
3. Run the update command again:
```bash
pacman -Syu
```
Repeat until no further updates are available.
---
## 3. Install Required Packages
Install the required development tools:
```bash
pacman -S make nasm xorriso git
```
---
## 4. Install QEMU for Windows
Download the Windows version of QEMU from:
- https://qemu.weilnetz.de/w64/
Install QEMU normally and make sure the installation directory is added to your Windows `PATH`.
Note that if it breaks when building, you need too add `qemu-img` to your `PATH`:
`export PATH="/c/Program Files/qemu:$PATH"`
You can verify the installation with:
```bash
qemu-system-x86_64 --version
```
---
## 5. Install the x86_64 ELF Cross Toolchain
Download the prebuilt `x86_64-elf` toolchain for Windows:
- https://github.com/lordmilko/i686-elf-tools/releases/download/15.2.0/x86_64-elf-tools-windows.zip
Extract the archive somewhere convenient.
---
## 6. Add the Toolchain to PATH
Inside the **MSYS2 UCRT64** terminal, add the toolchain binaries to your `PATH`:
```bash
export PATH="/c/Users/your/path/to/the/binaries/x86_64-elf-tools-windows/bin:$PATH"
```
To make this permanent, add the line to your `~/.bashrc` file:
```bash
echo 'export PATH="/c/Users/your/path/to/the/binaries/x86_64-elf-tools-windows/bin:$PATH"' >> ~/.bashrc
```
Then reload the shell:
```bash
source ~/.bashrc
```
---
## 7. Verify the Installation
Verify that the cross compiler is available:
```bash
x86_64-elf-gcc --version
```
You should also verify NASM and QEMU:
```bash
nasm -v
qemu-system-x86_64 --version
```
If all commands work, the development environment is correctly configured.

15
docs/build/usage.md vendored
View file

@ -33,23 +33,14 @@ 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: To test the generated ISO quickly without real hardware, use the QEMU emulator:
For MacOS:
```sh ```sh
make run-mac make run
``` ```
For Linux:
```sh This command automatically detects your operating system and invokes QEMU with specific arguments:
make run-linux
```
For Windows:
```sh
make run-windows
```
This command invokes QEMU with specific arguments:
- `-m 4G`: Allocates 4 Gigabytes of RAM. - `-m 4G`: Allocates 4 Gigabytes of RAM.
- `-cdrom boredos.iso`: Mounts the built OS image as a CD-ROM. - `-cdrom boredos.iso`: Mounts the built OS image as a CD-ROM.
- `-smp 4`: Enables 4 CPU cores. - `-smp 4`: Enables 4 CPU cores.
- `-drive file=disk.img...`: Attaches a raw disk image included in this release of BoredOS.
## Running on Bare Metal ## Running on Bare Metal

View file

@ -0,0 +1,27 @@
# BoredOS Installation
## Requirements
- Disk with at least **1 GB** (2,097,152 sectors)
- UEFI firmware
- A running BoredOS live environment (ISO)
## Quick Start (UEFI)
```
boredos_install --uefi /dev/sda
```
After installation, reboot and select the target disk from your firmware boot menu.
## Manual Steps
```
fdisk /dev/sda
mkfs_fat -F 32 -n EFI /dev/sda1
mkfs_fat -F 32 -n BOREDOS /dev/sda2
boredos_install --no-partition --no-format --uefi /dev/sda
```
See `install_guide.md` for a full walkthrough and `internals.md` for a deep dive into how the process works.

View file

@ -0,0 +1,72 @@
# Advanced Installation
## Skipping Steps
All major phases can be skipped independently:
```
boredos_install --no-partition --no-format --uefi /dev/sda
```
The destructive warning is only shown when `--no-partition` AND `--no-format` are both absent.
## `/mnt/boot` Operations
The installer mounts the ESP at `/mnt/boot` and unmounts it when finished.
## FAT32 Limitations
FAT32 does not support Unix file permissions, ownership, or timestamps. These attributes are **not** preserved during installation. This is expected and documented.
## Custom Device Names
Use `--esp-dev` and `--root-dev` to bypass auto-detection:
```
boredos_install --uefi --esp-dev sda1 --root-dev sda2 /dev/sda --no-partition --no-format
```
Provided device names are still validated via `sys_disk_get_info` before use.
### Step 1: Partitioning
Run `fdisk` interactively on your target device.
```bash
fdisk /dev/sda
```
Inside the interactive shell:
1. Type `n` to create a new partition (the ESP).
2. Press Enter for the default start offset (1mb).
3. Enter the size using suffixes like `b`, `mb`, or `gb` (e.g., `512mb` for a 512 MB ESP).
4. Type `n` again for the second partition (the Root).
5. Press Enter for the default start offset (aligned after the ESP).
6. Press Enter for the default size (rest of the disk).
7. Type `w` to write the partition table.
8. Type 'Q' to quit.
### Step 2: Formatting
Initialize the partitions with FAT32. Use the labels `EFI` and `BOREDOS` to match the expected system layout.
```bash
mkfs_fat -F 32 -n EFI /dev/sda1
mkfs_fat -F 32 -n BOREDOS /dev/sda2
```
### Step 3: Installation
The easiest way to perform the file copy and bootloader setup is to use the installer in "copy only" mode. This ensures that hidden flags (like the root detection file) are placed correctly.
```bash
boredos_install --no-partition --no-format --uefi --esp-dev sda1 /dev/sda
```
#### What this step does:
1. **Mounts** `/dev/sda2` to `/mnt` and `/dev/sda1` to `/mnt/boot`.
2. **Identifies** the root by creating an empty file at `/mnt/Library/.boredos_root`.
3. **Copies** the system structure: `/bin`, `/Library`, `/docs`, and `/root` are mirrored to `/mnt`.
4. **Deploys** the kernel and initrd: `boredos.elf` and `initrd.tar` are copied to the ESP (`/mnt/boot/`).
5. **Configures** Limine: Writes a `limine.conf` to the ESP and copies the EFI bootloader to `/mnt/boot/EFI/BOOT/BOOTX64.EFI`.
For a deep dive into why these steps are performed, see the [Installation Internals](internals.md) guide.

View file

@ -0,0 +1,30 @@
# Install Guide
## UEFI Installation
1. Boot from the BoredOS ISO.
2. Run `boredos_install --uefi /dev/sda`.
3. Type `y` when prompted and press Enter.
4. After completion, reboot and select the disk.
## limine.conf Notes
**UEFI** uses `boot():/boredos.elf` — the `boot():` URL scheme refers to the EFI System Partition.
**Root selection** uses `root=/dev/<partition>` in `cmdline:` to choose the writable root partition (for example `root=/dev/sda2`).
**Live vs disk override** supports `--live` and `--disk` in `cmdline:`. Use `--live` for ISO/USB live boots and `--disk` for installed systems.
## Options
| Flag | Description |
|---|---|
| `--no-partition` | Skip fdisk (use existing partitions) |
| `--no-format` | Skip mkfs (use existing filesystem) |
| `--no-files` | Skip file copy |
| `--no-bootloader` | Skip limine.conf and EFI file copy |
| `--esp-size N` | ESP size in MB (default: 512) |
| `--esp-dev DEV` | Explicit ESP device name |
| `--root-dev DEV` | Explicit root device name |
| `-y` | Auto-accept the destructive warning |

View file

@ -0,0 +1,57 @@
# Installation Internals: How, Why, and What
This document explains the technical details of the BoredOS installation process, the design decisions behind it, and the resulting system layout.
## How the installation works
The BoredOS installation follows a strict five-phase sequence:
1. **Partitioning (GPT/MBR)**: The disk is prepared with a partition table. For UEFI systems, we use GPT (GUID Partition Table) and create at least two partitions: an EFI System Partition (ESP) and a data partition.
2. **Formatting (FAT32)**: Both partitions are formatted with FAT32.
- The ESP is labeled `EFI`.
- The root partition is labeled `BOREDOS`.
3. **Mounting & Flagging**: The root partition is mounted to `/mnt`. A special marker file is created at `/Library/.boredos_root`. This file is used by the kernel during boot to differentiate between a persistent disk and a live ISO/RAM disk.
4. **System Mirroring**: The installer performs a recursive copy of the live system's essential including but not limited to:
- `/bin`: All userland binaries.
- `/Library`: System assets, fonts, icons, and configuration.
- `/docs`: Documentation and manuals.
- `/root`: The default user home directory.
5. **Bootloader Setup**:
- The ESP is mounted to `/mnt/boot`.
- The kernel (`boredos.elf`) and the initial RAM disk (`initrd.tar`) are copied to the root of the ESP.
- The Limine EFI binary is placed at `/EFI/BOOT/BOOTX64.EFI`.
- A `limine.conf` is generated with the correct `root=/dev/sdXX` parameter.
## Why it works this way
### FAT32 for Everything
BoredOS currently uses FAT32 as its primary filesystem for both the boot partition and the root partition.
- **Universal Support**: FAT32 is natively understood by UEFI firmware, making it the only choice for the ESP.
- **Simplicity**: By using FAT32 for the root partition as well, the kernel only needs one robust filesystem driver to handle both boot-time loading and runtime persistent storage.
- **Interoperability**: You can easily mount BoredOS partitions on other operating systems (Windows, Linux, macOS) to transfer files.
### The ESP/Root Split
Even though both use FAT32, we split the disk into two partitions to follow the UEFI specification. The ESP is meant to be small and strictly for bootloader files, while the root partition contains the entire userland. This separation allows for cleaner upgrades and multi-boot scenarios.
### Limine Bootloader
We chose [Limine](https://limine-bootloader.org/) because of its excellent support for modern protocols, its simplicity in configuration, and its lightweight footprint. It handles the transition from UEFI/BIOS environment to the kernel seamlessly.
## What the installation does
After a successful installation, your disk will look like this:
### Partition 1: EFI System Partition (ESP)
- `/boredos.elf`: The actual kernel binary.
- `/initrd.tar`: The basic system image loaded into RAM at boot.
- `/limine.conf`: The bootloader configuration.
- `/EFI/BOOT/BOOTX64.EFI`: The bootloader entry point for UEFI firmware.
### Partition 2: BoredOS Root
- `/bin/`: Executables.
- `/Library/`: System data.
- `.boredos_root`: Hidden marker file.
- `fonts/`, `images/`, `man/`: System resources.
- `/docs/`: System documentation.
- `/root/`: Persistent user files.

View file

@ -1,33 +0,0 @@
# 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
```

90
docs/usage/commands/du.md Normal file
View file

@ -0,0 +1,90 @@
# du
`du` (disk usage) reports the disk space used by files and directories.
## Usage
```sh
du [OPTIONS]... [FILE]...
```
## Description
By default, `du` prints human-readable sizes for each file and directory it encounters, starting from the current directory (`.`) if no path is given.
## Options
| Option | Description |
| :--- | :--- |
| `-s, --summarize` | Show only a total for each argument, suppressing per-entry output. |
| `-a, --all` | Write counts for all files, not just directories. |
| `-d, --max-depth=N` | Stop at depth N; show only entries at or above depth N. |
| `-c, --total` | Print a grand total after all arguments have been processed. |
| `-b, --bytes` | Print sizes in exact bytes instead of human-readable units. |
| `-H, --human-readable` | Accepted for compatibility; human-readable is the default. |
| `--help` | Display usage information and exit. |
## Output Format
Each line shows a size followed by the path:
```
SIZE PATH
```
Sizes are formatted as `B`, `KB`, `MB`, or `GB` by default, with one decimal place when appropriate (e.g., `1.5 GB`). The `-b` option overrides this to show exact byte counts.
## Examples
Show disk usage for the current directory:
```sh
du
```
Show disk usage for a specific path:
```sh
du /bin
```
Show only totals per argument (`-s`):
```sh
du -s /bin /home
```
Show all files and directories recursively (`-a`):
```sh
du -a /bin
```
Limit output to depth 1 (`-d`):
```sh
du -d 1 /
```
Print a grand total after processing (`-c`):
```sh
du -c /bin /home
```
Show exact byte counts (`-b`):
```sh
du -b /bin
```
## How It Works
`du` uses `sys_get_file_info()` to read file sizes and `sys_list()` to enumerate directory contents recursively. The command skips the synthetic `.` and `..` entries and continues processing remaining paths if one path is inaccessible, printing an error for the failed path.
The size reported is the **apparent file size** (the logical size stored in the directory entry), not the allocated disk blocks. This is consistent with how BoredOS reports file sizes through the filesystem API.
## Exit Status
- `0`: Success
- `1`: One or more paths could not be accessed or listed

View file

@ -0,0 +1,106 @@
# lsblk
`lsblk` lists the block devices detected by BoredOS, including whole disks and their partitions.
## Usage
```sh
lsblk
lsblk /dev/sda
lsblk -r
lsblk --json
```
## Output
By default, `lsblk` prints a compact tree view:
```text
/dev/sda 2 GB disk
└─ sda1 2 GB part FAT32 BOREDOS
```
Fields shown by the default output:
- device name, such as `/dev/sda` or `sda1`
- human-readable size, such as `512 MB` or `2 GB`
- device type, either `disk` or `part`
- filesystem type, currently `FAT32` when detected
- volume label when available
- `[ESP]` flag for EFI System Partitions
> [!NOTE]
> Mount points are not shown yet because BoredOS does not currently expose mountpoint information through the disk info syscall.
## Options
| Option | Description |
| :--- | :--- |
| `-r` | Print raw output without tree characters. |
| `--json` | Print machine-readable JSON output. |
| `/dev/DEVICE` | Show only one disk or partition. |
## Examples
List all block devices:
```sh
lsblk
```
Example output:
```text
/dev/sda 2 GB disk
└─ sda1 2 GB part FAT32 BOREDOS
/dev/sdb 16 GB disk
```
Show one disk and its partitions:
```sh
lsblk /dev/sda
```
Example output:
```text
/dev/sda 2 GB disk
└─ sda1 2 GB part FAT32 BOREDOS
```
Print raw output for scripts:
```sh
lsblk -r
```
Example output:
```text
/dev/sda 2GB disk
/dev/sda1 2GB part FAT32 BOREDOS
```
Print JSON output:
```sh
lsblk --json
```
Example output:
```json
{"devices":[{"name":"/dev/sda","size":"2 GB","type":"disk","fstype":"","label":"","flags":[],"children":[{"name":"/dev/sda1","size":"2 GB","type":"part","fstype":"FAT32","label":"BOREDOS","flags":[]}]}]}
```
## How It Works
`lsblk` reads disk metadata through the disk syscalls exposed by BoredOS:
- `sys_disk_get_count()` gets the number of registered block devices.
- `sys_disk_get_info()` reads each device's name, size, type, FAT32 status, label, and flags.
The command treats non-partition entries as parent disks, then groups partition entries under the matching disk name. For example, `sda1` is displayed under `/dev/sda`.
Sizes are calculated from sector counts using 512-byte sectors, then formatted as `KB`, `MB`, or `GB`.

100
docs/usage/settings.md Normal file
View file

@ -0,0 +1,100 @@
# Settings
Settings is a system configuration application providing a graphical interface to manage BoredOS preferences across multiple categories.
## Main Menu
The Settings application presents seven configuration categories, each with its own icon and panel:
- **Wallpaper** — Manage desktop background images and patterns
- **Network** — Configure network interfaces and connectivity
- **Desktop** — Control desktop layout and icon alignment
- **Mouse** — Adjust mouse speed and cursor appearance
- **Fonts** — Browse and select system fonts
- **Display** — Configure screen resolution
- **Keyboard** — Select keyboard layout
## Wallpaper
### Image Selection
- Browse wallpapers stored in `/Library/images/Wallpapers/`
- Supported formats: JPEG (`.jpg`)
- Thumbnails (80×50 pixels) are generated for preview
- Selected wallpaper is applied immediately to the desktop background
### Patterns
The Wallpaper panel provides built-in pattern options:
- **Lumberjack Pattern** — Checkered pattern with red, dark grey, and black colors
- **Blue Diamond Pattern** — Geometric diamond design
Patterns are rendered procedurally (128×128 pixels) and can be applied as alternatives to image wallpapers.
### Color Settings
Six color presets are available for quick selection, with RGB textbox inputs for custom color values.
## Network
### Configuration
- Set static IP address via textbox input
- Configure DNS server address
- Network status is displayed after initialization
- Settings are applied through the `NET_INIT`, `NET_SET_IP`, and `NET_SET_DNS` controls
## Desktop
### Layout Control
- **Snap to Grid** — Enable/disable automatic icon alignment to grid positions
- **Auto Align** — Automatically reorganize icons when enabled
- **Columns** — Adjust maximum number of columns for icon layout (configurable with +/- buttons)
- **Rows per Column** — Set maximum rows within each column
Desktop grid settings are stored as `desktop_max_rows_per_col` and `desktop_max_cols`.
## Mouse
### Cursor Control
- **Mouse Speed** — Adjust pointer movement sensitivity
- **Cursor Scale** — Increase or decrease cursor size using +/ buttons
- Settings communicates with the kernel WM using `SYSTEM_GET_CURSOR_SCALE` and `SYSTEM_SET_CURSOR_SCALE` syscalls
- Cursor changes are applied instantly and visibly in real-time
## Fonts
### System Fonts
- Browse available fonts from `/Library/Fonts/`
- Font list is dynamically loaded with scrollbar for navigation
- Each font displays an icon and name
- Fonts are listed with entry structures containing path and name information
## Display
### Resolution Selection
- Choose from dynamic resolution options based on physical screen size:
- 50% of screen resolution
- 75% of screen resolution
- Full screen resolution (100%)
- Custom resolution entry via textbox (width and height)
- Apply button commits the selected resolution change
## Keyboard
### Layout Selection
- Available keyboard layouts can be selected from a dropdown
- Layout state is maintained as `keyboard_layout`
- Selection applies to system-wide keyboard input
---
[Return to Documentation Index](../README.md)

View file

@ -7,11 +7,88 @@ The BoredOS Terminal provides a powerful command-line interface (CLI) for advanc
The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features: 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. - **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). - **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries).
- **Output Redirection**: - **Redirection**:
- `command > file`: Write output to a new file (or overwrite existing). - `command > file`: Write output to a new file (or overwrite existing).
- `command >> file`: Append output to an existing file. - `command >> file`: Append output to an existing file.
- `command < file`: Use a file as command input.
- **Piping**: - **Piping**:
- `command1 | command2`: Pass the output of the first command as input to the second. - `command1 | command2`: Pass the output of the first command as input to the second.
- **Command Chaining**:
- `cmd1 ; cmd2`: Run `cmd2` after `cmd1`.
- `cmd1 && cmd2`: Run `cmd2` only if `cmd1` succeeds.
- `cmd1 || cmd2`: Run `cmd2` only if `cmd1` fails.
- **Background Launch**:
- `command &`: Start command without blocking the prompt.
## Shell Operators & Scripting
Bsh provides a suite of operators for managing I/O and controlling execution flow.
### I/O Redirection
Redirection allows you to change where a command reads its input from or writes its output to by manipulating kernel-level file descriptors (FDs).
| Operator | Action | Description |
| :--- | :--- | :--- |
| `>` | **Overwrite** | Redirects standard output (FD 1) to a file, creating it if it doesn't exist or truncating it if it does. |
| `>>` | **Append** | Redirects standard output (FD 1) to a file, appending new data to the end without clearing existing content. |
| `<` | **Input** | Redirects standard input (FD 0) to read from a file instead of the terminal. |
**Example:**
```bash
echo "Hello World" > greetings.txt
echo "Second line" >> greetings.txt
cat < greetings.txt
```
**How it works:**
The shell uses `sys_open` to obtain a handle for the target file, then uses `sys_dup2` to replace the process's standard FD (0 or 1) with the new file handle. This ensures the command's standard library calls (like `printf` or `scanf`) interact with the file instead of the terminal.
### Pipelines (`|`)
Pipelines connect the output of one command directly to the input of another.
**Usage:** `cmd1 | cmd2 | cmd3`
**Example:**
```bash
ls /bin | cat | cat
```
**How it works:**
The shell creates an anonymous pipe using `sys_pipe`, which returns two FDs: a read end and a write end.
- The shell duplicates the **write end** to FD 1 for the first command.
- The shell duplicates the **read end** to FD 0 for the second command.
- Both commands run in parallel, and the kernel manages the data buffer between them.
### Execution Control
| Operator | Name | Description |
| :--- | :--- | :--- |
| `;` | **Semicolon** | Executes commands sequentially. `cmd2` runs only after `cmd1` finishes. |
| `&&` | **Logical AND** | Executes `cmd2` only if `cmd1` returns a success status (exit code 0). |
| `||` | **Logical OR** | Executes `cmd2` only if `cmd1` fails (returns a non-zero exit code). |
**Example:**
```bash
# Compile and run only on success
make && ./boredos.elf
# Recover from a missing command
missing_tool || echo "Tool not found, using fallback"
```
### Background Execution (`&`)
Appending `&` to a command tells the shell not to wait for the process to complete before returning to the prompt.
**How it works:**
Normally, the shell calls `sys_waitpid` to block until a child process exits. With `&`, the shell skips this wait, allowing the process to run asynchronously while you continue using the terminal.
Operator precedence follows common POSIX shell rules:
1. Redirection and pipelines (`<`, `>`, `>>`, `|`)
2. Conditionals (`&&`, `||`)
3. List separators (`;`, `&`)
### Bsh Configuration ### Bsh Configuration
@ -49,11 +126,13 @@ Below are some of the most used commands available in `/bin`:
| `ls` | List files and directories in the current path. | | `ls` | List files and directories in the current path. |
| `cd` | Change the current working directory. | | `cd` | Change the current working directory. |
| `cat` | Display the contents of a file. | | `cat` | Display the contents of a file. |
| `ls` | List directory contents. |
| `rm` | Remove a file. | | `rm` | Remove a file. |
| `mkdir` | Create a new directory. | | `mkdir` | Create a new directory. |
| `man` | View the manual for a specific command (e.g., `man ls`). | | `man` | View the manual for a specific command (e.g., `man ls`). |
| `sysfetch` | Display system and hardware information. | | `lsblk` | List block devices and partitions. |
| `du` | Report disk usage for files and directories. |
| `sysfetch` | Display system information and BoredOS branding. |
| `time` | Measure command execution time. |
--- ---

BIN
limine/BOOTAA64.EFI Normal file

Binary file not shown.

BIN
limine/BOOTIA32.EFI Normal file

Binary file not shown.

BIN
limine/BOOTLOONGARCH64.EFI Normal file

Binary file not shown.

BIN
limine/BOOTRISCV64.EFI Normal file

Binary file not shown.

BIN
limine/BOOTX64.EFI Normal file

Binary file not shown.

22
limine/LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (C) 2019-2026 Mintsuki and contributors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

19
limine/Makefile Normal file
View file

@ -0,0 +1,19 @@
.POSIX:
SHELL=/bin/sh
CC=cc
CFLAGS=-g -O2 -pipe
CPPFLAGS=
LDFLAGS=
LIBS=
.PHONY: all
all: limine
.PHONY: clean
clean:
rm -f limine limine.exe
limine: limine.c
$(CC) $(CFLAGS) -std=c99 $(CPPFLAGS) $(LDFLAGS) $< $(LIBS) -o $@

BIN
limine/limine-bios-cd.bin Normal file

Binary file not shown.

1368
limine/limine-bios-hdd.h Normal file

File diff suppressed because it is too large Load diff

BIN
limine/limine-bios-pxe.bin Normal file

Binary file not shown.

BIN
limine/limine-bios.sys Normal file

Binary file not shown.

BIN
limine/limine-uefi-cd.bin Normal file

Binary file not shown.

1417
limine/limine.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleIdentifier</key>
<string>com.apple.xcode.dsym.limine</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>dSYM</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -0,0 +1,5 @@
---
triple: 'arm64-apple-darwin'
binary-path: limine
relocations: []
...

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View file

@ -49,7 +49,6 @@ static void kconsole_putc_nolock(char c) {
cursor_x = 10; cursor_x = 10;
cursor_y += CHAR_HEIGHT; cursor_y += CHAR_HEIGHT;
kconsole_scroll(); kconsole_scroll();
graphics_flip_buffer();
return; return;
} }
@ -93,8 +92,5 @@ void kconsole_write(const char *s) {
while (*s) { while (*s) {
kconsole_putc_nolock(*s++); kconsole_putc_nolock(*s++);
} }
// Flip once after a write batch to keep console updates coherent.
graphics_flip_buffer();
spinlock_release_irqrestore(&console_lock, flags); spinlock_release_irqrestore(&console_lock, flags);
} }

View file

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

View file

@ -5,24 +5,54 @@
#include "wm.h" #include "wm.h"
#include "io.h" #include "io.h"
void k_memset(void *dest, int val, size_t len) { #include "../drivers/ACPI/acpi.h"
void memset(void *dest, int val, size_t len) {
unsigned char *ptr = (unsigned char *)dest; unsigned char *ptr = (unsigned char *)dest;
while (len-- > 0) *ptr++ = (unsigned char)val; while (len-- > 0) *ptr++ = (unsigned char)val;
} }
void k_memcpy(void *dest, const void *src, size_t len) { void memcpy(void *dest, const void *src, size_t len) {
unsigned char *d = (unsigned char *)dest; unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src; const unsigned char *s = (const unsigned char *)src;
while (len-- > 0) *d++ = *s++; while (len-- > 0) *d++ = *s++;
} }
size_t k_strlen(const char *str) { int memcmp(const void *str1, const void *str2, size_t count) {
register const unsigned char *s1 = (const unsigned char*)str1;
register const unsigned char *s2 = (const unsigned char*)str2;
while (count-- > 0) {
if (*s1++ != *s2++)
return s1[-1] < s2[-1] ? -1 : 1;
}
return 0;
}
void *memmove(void *dest, const void *src, uint64_t n) {
uint8_t *pdest = (uint8_t *)dest;
const uint8_t *psrc = (const uint8_t *)src;
if (src > dest) {
for (uint64_t i = 0; i < n; i++) {
pdest[i] = psrc[i];
}
} else if (src < dest) {
for (uint64_t i = n; i > 0; i--) {
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
size_t strlen(const char *str) {
size_t len = 0; size_t len = 0;
while (str[len]) len++; while (str[len]) len++;
return len; return len;
} }
int k_strcmp(const char *s1, const char *s2) { int strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) { while (*s1 && (*s1 == *s2)) {
s1++; s1++;
s2++; s2++;
@ -30,7 +60,7 @@ int k_strcmp(const char *s1, const char *s2) {
return *(const unsigned char*)s1 - *(const unsigned char*)s2; return *(const unsigned char*)s1 - *(const unsigned char*)s2;
} }
int k_strncmp(const char *s1, const char *s2, size_t n) { int strncmp(const char *s1, const char *s2, size_t n) {
while (n && *s1 && (*s1 == *s2)) { while (n && *s1 && (*s1 == *s2)) {
s1++; s1++;
s2++; s2++;
@ -40,12 +70,26 @@ int k_strncmp(const char *s1, const char *s2, size_t n) {
return *(const unsigned char*)s1 - *(const unsigned char*)s2; return *(const unsigned char*)s1 - *(const unsigned char*)s2;
} }
void k_strcpy(char *dest, const char *src) { void strcpy(char *dest, const char *src) {
while (*src) *dest++ = *src++; while (*src) *dest++ = *src++;
*dest = 0; *dest = 0;
} }
int k_atoi(const char *str) { char *strncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n && src[i] != '\0'; i++) {
dest[i] = src[i];
}
for (; i < n; i++) {
dest[i] = '\0';
}
return dest;
}
int atoi(const char *str) {
int res = 0; int res = 0;
int sign = 1; int sign = 1;
if (*str == '-') { sign = -1; str++; } if (*str == '-') { sign = -1; str++; }
@ -56,7 +100,7 @@ int k_atoi(const char *str) {
return res * sign; return res * sign;
} }
void k_itoa(int n, char *buf) { void itoa(int n, char *buf) {
if (n == 0) { if (n == 0) {
buf[0] = '0'; buf[1] = 0; return; buf[0] = '0'; buf[1] = 0; return;
} }
@ -76,7 +120,7 @@ void k_itoa(int n, char *buf) {
} }
} }
void k_itoa_hex(uint64_t n, char *buf) { void itoa_hex(uint64_t n, char *buf) {
const char *digits = "0123456789ABCDEF"; const char *digits = "0123456789ABCDEF";
if (n == 0) { if (n == 0) {
buf[0] = '0'; buf[0] = '0';
@ -118,9 +162,7 @@ void k_reboot(void) {
} }
void k_shutdown(void) { void k_shutdown(void) {
outw(0xB004, 0x2000); // QEMU older / some pc machines acpi_shutdown();
outw(0x604, 0x2000); // QEMU newer (i440fx/q35)
outw(0x4004, 0x3400); // VirtualBox fallback
} }
volatile uint64_t beep_end_tick = 0; volatile uint64_t beep_end_tick = 0;

View file

@ -9,15 +9,18 @@
#include <stdbool.h> #include <stdbool.h>
// Kernel string utilities // Kernel string utilities
void k_memset(void *dest, int val, size_t len); void *memmove(void *dest, const void *src, uint64_t n);
void k_memcpy(void *dest, const void *src, size_t len); void memset(void *dest, int val, size_t len);
size_t k_strlen(const char *str); void memcpy(void *dest, const void *src, size_t len);
int k_strcmp(const char *s1, const char *s2); int memcmp (const void *str1, const void *str2, size_t count);
int k_strncmp(const char *s1, const char *s2, size_t n); size_t strlen(const char *str);
void k_strcpy(char *dest, const char *src); int strcmp(const char *s1, const char *s2);
int k_atoi(const char *str); int strncmp(const char *s1, const char *s2, size_t n);
void k_itoa(int n, char *buf); void strcpy(char *dest, const char *src);
void k_itoa_hex(uint64_t n, char *buf); char *strncpy(char *dest, const char *src, size_t n);
int atoi(const char *str);
void itoa(int n, char *buf);
void itoa_hex(uint64_t n, char *buf);
// Kernel timing utilities // Kernel timing utilities
void k_delay(int iterations); void k_delay(int iterations);

View file

@ -26,6 +26,7 @@
#include "smp.h" #include "smp.h"
#include "work_queue.h" #include "work_queue.h"
#include "lapic.h" #include "lapic.h"
#include "panic.h"
#include "fs/sysfs.h" #include "fs/sysfs.h"
#include "fs/procfs.h" #include "fs/procfs.h"
#include "fs/bootfs.h" #include "fs/bootfs.h"
@ -33,6 +34,8 @@
#include "sys/module_manager.h" #include "sys/module_manager.h"
#include "sys/bootfs_state.h" #include "sys/bootfs_state.h"
#include "input/keymap.h" #include "input/keymap.h"
#include "input/keyboard.h"
#include "../drivers/ACPI/acpi.h"
extern void sysfs_init_subsystems(void); extern void sysfs_init_subsystems(void);
@ -77,6 +80,12 @@ static volatile struct limine_kernel_file_request kernel_file_request = {
.revision = 0 .revision = 0
}; };
__attribute__((used, section(".requests")))
volatile struct limine_rsdp_request acpi_rsdp_request = {
.id = LIMINE_RSDP_REQUEST,
.revision = 0
};
__attribute__((used, section(".requests_start"))) __attribute__((used, section(".requests_start")))
static volatile struct limine_request *const requests_start_marker[] = { static volatile struct limine_request *const requests_start_marker[] = {
(struct limine_request *)&framebuffer_request, (struct limine_request *)&framebuffer_request,
@ -85,6 +94,7 @@ static volatile struct limine_request *const requests_start_marker[] = {
(struct limine_request *)&smp_request, (struct limine_request *)&smp_request,
(struct limine_request *)&bootloader_info_request, (struct limine_request *)&bootloader_info_request,
(struct limine_request *)&kernel_file_request, (struct limine_request *)&kernel_file_request,
(struct limine_request *)&acpi_rsdp_request,
NULL NULL
}; };
@ -124,7 +134,7 @@ void serial_write(const char *str) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_num_locked(uint32_t n) { void serial_write_num_locked(uint32_t n) {
if (n >= 10) serial_write_num_locked(n / 10); if (n >= 10) serial_write_num_locked(n / 10);
char c = '0' + (n % 10); char c = '0' + (n % 10);
while ((inb(0x3F8 + 5) & 0x20) == 0); while ((inb(0x3F8 + 5) & 0x20) == 0);
@ -138,7 +148,7 @@ void serial_write_num(uint32_t n) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_hex_locked(uint64_t n) { void serial_write_hex_locked(uint64_t n) {
char *hex = "0123456789ABCDEF"; char *hex = "0123456789ABCDEF";
if (n >= 16) serial_write_hex_locked(n / 16); if (n >= 16) serial_write_hex_locked(n / 16);
char c = hex[n % 16]; char c = hex[n % 16];
@ -174,34 +184,25 @@ void log_fail(const char *msg) {
} }
static void print_verbose_boot_banner(void) { static void print_verbose_boot_banner(void) {
kconsole_set_color(0xFFB589D6); kconsole_set_color(0xFF473ba3);
serial_write("==================== "); serial_write(" @@@@\n");
kconsole_set_color(0xFFFFFFFF); serial_write(" @@@@@@@\n");
serial_write("__ ____ ____ \n"); serial_write(" @@@@@@\n");
serial_write(" @@@@@@@\n");
kconsole_set_color(0xFFB589D6); serial_write(" @@@@@@@ @@@@@@\n");
serial_write("=================== "); serial_write(" @@@@@@ @@@@@@@@@@@@\n");
kconsole_set_color(0xFFFFFFFF); serial_write(" @@@@@@ @@@@@@@@@@@@@@a\n");
serial_write("/ /_ / __ \\/ ___\\\n"); serial_write(" @@@@@@@@@@@X @@@@@@@@w\n");
serial_write(" @@@@@@@@ @@@@@@@\n");
kconsole_set_color(0xFF569CD6); serial_write(" @@@@@@M @@@@@@\n");
serial_write("================== "); serial_write(" @@@@@@@ @@@@@@\n");
kconsole_set_color(0xFFFFFFFF); serial_write(" @@@@@@@ @@@@@@@@\n");
serial_write("/ __ \\/ / / /\\___ \\\n"); serial_write(" @@@@@@@@@@@@@@@@@@\n");
serial_write(" i@@@@@@@@@@@@@@@\n");
kconsole_set_color(0xFF569CD6); serial_write(" @@@@@@@\n");
serial_write("================= "); serial_write(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
kconsole_set_color(0xFFFFFFFF); serial_write(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
serial_write("/ /_/ / /_/ /____/ /\n"); serial_write(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
kconsole_set_color(0xFF4EC9B0);
serial_write("================ ");
kconsole_set_color(0xFFFFFFFF);
serial_write("/_.___/\\____//_____/ \n");
kconsole_set_color(0xFF4EC9B0);
serial_write("=============== \n");
kconsole_set_color(0xFFFFFFFF); kconsole_set_color(0xFFFFFFFF);
serial_write("\n"); serial_write("\n");
} }
@ -234,6 +235,78 @@ static void fat32_mkdir_recursive(const char *path) {
} }
} }
static bool cmdline_has_flag(const char *cmdline, const char *flag) {
if (!cmdline || !flag || !flag[0]) return false;
int flag_len = (int)strlen(flag);
const char *p = cmdline;
while (*p) {
while (*p == ' ') p++;
if (!*p) break;
const char *start = p;
while (*p && *p != ' ') p++;
int len = (int)(p - start);
if (len == flag_len && strncmp(start, flag, (size_t)flag_len) == 0) return true;
}
return false;
}
static bool cmdline_read_value(const char *cmdline, const char *key, char *out, int out_len) {
if (!cmdline || !key || !out || out_len <= 1) return false;
int key_len = (int)strlen(key);
const char *p = cmdline;
while (*p) {
while (*p == ' ') p++;
if (!*p) break;
if (strncmp(p, key, (size_t)key_len) == 0) {
const char *val = p + key_len;
int i = 0;
while (*val && *val != ' ' && i < out_len - 1) {
out[i++] = *val++;
}
out[i] = '\0';
return i > 0;
}
while (*p && *p != ' ') p++;
}
return false;
}
static void boot_parse_cmdline(const char *cmdline, uint32_t media_type) {
g_bootfs_state.boot_flags = 0;
g_bootfs_state.root_device[0] = '\0';
char root_arg[32];
if (cmdline_read_value(cmdline, "root=", root_arg, (int)sizeof(root_arg))) {
const char *dev = root_arg;
if (dev[0] == '/' && dev[1] == 'd' && dev[2] == 'e' && dev[3] == 'v' && dev[4] == '/') {
dev += 5;
}
int i = 0;
while (dev[i] && i < (int)sizeof(g_bootfs_state.root_device) - 1) {
g_bootfs_state.root_device[i] = dev[i];
i++;
}
g_bootfs_state.root_device[i] = '\0';
if (i > 0) g_bootfs_state.boot_flags |= BOOT_FLAG_ROOT_SET;
}
bool force_live = cmdline_has_flag(cmdline, "--live");
bool force_disk = cmdline_has_flag(cmdline, "--disk");
if (force_live) {
g_bootfs_state.boot_flags |= BOOT_FLAG_LIVE | BOOT_FLAG_FORCED;
} else if (force_disk) {
g_bootfs_state.boot_flags |= BOOT_FLAG_DISK | BOOT_FLAG_FORCED;
} else if (g_bootfs_state.boot_flags & BOOT_FLAG_ROOT_SET) {
g_bootfs_state.boot_flags |= BOOT_FLAG_DISK;
} else if (media_type == LIMINE_MEDIA_TYPE_OPTICAL || media_type == LIMINE_MEDIA_TYPE_TFTP) {
g_bootfs_state.boot_flags |= BOOT_FLAG_LIVE;
} else {
g_bootfs_state.boot_flags |= BOOT_FLAG_DISK;
}
}
void kmain(void) { void kmain(void) {
init_serial(); init_serial();
vfs_init(); vfs_init();
@ -302,27 +375,12 @@ void kmain(void) {
kconsole_set_color(0xFFFFFF55); kconsole_set_color(0xFFFFFF55);
serial_write("Welcome to BoredOS!\n"); serial_write("Welcome to BoredOS!\n");
kconsole_set_color(0xFFFFFFFF); kconsole_set_color(0xFFFFFFFF);
acpi_init();
process_init(); process_init();
fat32_init(); fat32_init();
log_ok("FAT32 ready"); log_ok("FAT32 ready");
fat32_mkdir("/bin");
fat32_mkdir("/Library");
fat32_mkdir("/Library/images");
fat32_mkdir("/Library/images/Wallpapers");
fat32_mkdir("/Library/images/gif");
fat32_mkdir("/Library/Fonts");
fat32_mkdir("/Library/DOOM");
fat32_mkdir("/Library/conf");
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(); sysfs_init_subsystems();
vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL); vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL);
@ -332,10 +390,10 @@ void kmain(void) {
if (bootloader_info_request.response != NULL) { if (bootloader_info_request.response != NULL) {
if (bootloader_info_request.response->name) { if (bootloader_info_request.response->name) {
k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name); strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name);
} }
if (bootloader_info_request.response->version) { if (bootloader_info_request.response->version) {
k_strcpy(g_bootfs_state.bootloader_version, bootloader_info_request.response->version); strcpy(g_bootfs_state.bootloader_version, bootloader_info_request.response->version);
} }
} }
@ -346,6 +404,14 @@ void kmain(void) {
serial_write(" bytes\n"); serial_write(" bytes\n");
} }
if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) {
const char *cmdline = kernel_file_request.response->kernel_file->cmdline;
uint32_t media_type = kernel_file_request.response->kernel_file->media_type;
boot_parse_cmdline(cmdline, media_type);
} else {
boot_parse_cmdline(NULL, LIMINE_MEDIA_TYPE_GENERIC);
}
extern uint32_t wm_get_ticks(void); extern uint32_t wm_get_ticks(void);
g_bootfs_state.boot_time_ms = wm_get_ticks(); g_bootfs_state.boot_time_ms = wm_get_ticks();
@ -372,6 +438,7 @@ void kmain(void) {
if (path_len >= 5 && path[path_len-4] == '.' && path[path_len-3] == 't' && if (path_len >= 5 && path[path_len-4] == '.' && path[path_len-3] == 't' &&
path[path_len-2] == 'a' && path[path_len-1] == 'r') { path[path_len-2] == 'a' && path[path_len-1] == 'r') {
g_bootfs_state.initrd_size = mod->size; g_bootfs_state.initrd_size = mod->size;
g_bootfs_state.initrd_ptr = mod->address;
serial_write("[INIT] -> Initrd detected\n"); serial_write("[INIT] -> Initrd detected\n");
} }
} }
@ -445,6 +512,7 @@ void kmain(void) {
smp_init(NULL); smp_init(NULL);
} }
wm_init(); wm_init();
asm volatile("sti"); asm volatile("sti");
@ -452,10 +520,5 @@ void kmain(void) {
extern void bootfs_refresh_from_disk(void); extern void bootfs_refresh_from_disk(void);
bootfs_refresh_from_disk(); bootfs_refresh_from_disk();
while (1) { wm_run_loop();
wm_process_input();
wm_process_deferred_thumbs();
wallpaper_process_pending();
asm("hlt");
}
} }

View file

@ -17,115 +17,84 @@ static void draw_string_centered(int y, const char *s, uint32_t color) {
} }
void kernel_panic(registers_t *regs, const char *error_name) { void kernel_panic(registers_t *regs, const char *error_name) {
// Disable interrupts to prevent nested panics
asm volatile("cli"); asm volatile("cli");
// Clear back buffer to black
graphics_clear_back_buffer(0x00000000); graphics_clear_back_buffer(0x00000000);
int sh = get_screen_height(); int sh = get_screen_height();
int cy = sh / 2; int cy = sh / 2;
// Draw header // Header
draw_string_centered(cy - 150, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0xFFFF0000); draw_string_centered(cy - 150, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0xFFFF0000);
draw_string_centered(cy - 130, "KERNEL EXCEPTION OCCURRED", 0xFFFFFFFF); draw_string_centered(cy - 130, "KERNEL PANIC", 0xFFFFFFFF);
draw_string_centered(cy - 110, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0xFFFF0000); draw_string_centered(cy - 110, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", 0xFFFF0000);
// Error name // Error name
char err_buf[256]; char buf[256];
int pos = 0; int pos = 0;
const char *prefix = "Exception: "; const char *prefix = "Error: ";
while(prefix[pos]) { err_buf[pos] = prefix[pos]; pos++; } while (prefix[pos]) { buf[pos] = prefix[pos]; pos++; }
int i = 0; int i = 0;
while(error_name[i]) { err_buf[pos++] = error_name[i++]; } while (error_name[i]) { buf[pos++] = error_name[i++]; }
err_buf[pos] = 0; buf[pos] = 0;
draw_string_centered(cy - 70, err_buf, 0xFFFFCC00); draw_string_centered(cy - 70, buf, 0xFFFFCC00);
// Details - simplified centering by drawing them as a block or individually centered if (regs != NULL) {
// Exception details
char info_buf[64]; char info_buf[64];
const char *digits = "0123456789ABCDEF";
// Vector #define FMT_HEX(prefix_str, value, color, y_offset) \
pos = 0; do { \
prefix = "Vector: "; int pos = 0; \
while(prefix[pos]) { info_buf[pos] = prefix[pos]; pos++; } const char *pfx = (prefix_str); \
uint64_t v = regs->int_no; while (pfx[pos]) { info_buf[pos] = pfx[pos]; pos++; } \
const char* digits = "0123456789ABCDEF"; info_buf[pos++] = '0'; info_buf[pos++] = 'x'; \
info_buf[pos++] = '0'; info_buf[pos++] = 'x'; uint64_t _v = (value); \
for (int i = 15; i >= 0; i--) { for (int _i = 15; _i >= 0; _i--) { \
info_buf[pos + i] = digits[v & 0xF]; info_buf[pos + _i] = digits[_v & 0xF]; _v >>= 4; \
v >>= 4; } \
} info_buf[pos + 16] = 0; \
info_buf[pos + 16] = 0; draw_string_centered((y_offset), info_buf, (color)); \
draw_string_centered(cy - 40, info_buf, 0xFFFFFFFF); } while (0)
// Error Code FMT_HEX("Vector: ", regs->int_no, 0xFFFFFFFF, cy - 40);
pos = 0; FMT_HEX("Error Code: ", regs->err_code, 0xFFFFFFFF, cy - 20);
prefix = "Error Code: "; FMT_HEX("RIP: ", regs->rip, 0xFFFFFFFF, cy);
while(prefix[pos]) { info_buf[pos] = prefix[pos]; pos++; }
v = regs->err_code;
info_buf[pos++] = '0'; info_buf[pos++] = 'x';
for (int i = 15; i >= 0; i--) {
info_buf[pos + i] = digits[v & 0xF];
v >>= 4;
}
info_buf[pos + 16] = 0;
draw_string_centered(cy - 20, info_buf, 0xFFFFFFFF);
// RIP
pos = 0;
prefix = "RIP: ";
while(prefix[pos]) { info_buf[pos] = prefix[pos]; pos++; }
v = regs->rip;
info_buf[pos++] = '0'; info_buf[pos++] = 'x';
for (int i = 15; i >= 0; i--) {
info_buf[pos + i] = digits[v & 0xF];
v >>= 4;
}
info_buf[pos + 16] = 0;
draw_string_centered(cy, info_buf, 0xFFFFFFFF);
// CR2 for page faults
if (regs->int_no == 14) { if (regs->int_no == 14) {
uint64_t cr2; uint64_t cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2)); asm volatile("mov %%cr2, %0" : "=r"(cr2));
pos = 0; FMT_HEX("CR2: ", cr2, 0xFFFF5555, cy + 20);
prefix = "CR2: "; }
while(prefix[pos]) { info_buf[pos] = prefix[pos]; pos++; }
info_buf[pos++] = '0'; info_buf[pos++] = 'x'; #undef FMT_HEX
for (int i = 15; i >= 0; i--) {
info_buf[pos + i] = digits[cr2 & 0xF];
cr2 >>= 4;
}
info_buf[pos + 16] = 0;
draw_string_centered(cy + 20, info_buf, 0xFFFF5555);
} }
// Message
draw_string_centered(cy + 100, "The system has been halted to prevent damage.", 0xFFFFFFFF); draw_string_centered(cy + 100, "The system has been halted to prevent damage.", 0xFFFFFFFF);
draw_string_centered(cy + 120, "Please restart your computer.", 0xFFAAAAAA); draw_string_centered(cy + 120, "Please restart your computer.", 0xFFAAAAAA);
// Flip buffer to screen
graphics_mark_screen_dirty(); graphics_mark_screen_dirty();
graphics_flip_buffer(); graphics_flip_buffer();
char hex_buf[17];
serial_write("\n*** KERNEL PANIC ***\n"); serial_write("\n*** KERNEL PANIC ***\n");
serial_write(error_name); serial_write(error_name);
serial_write("\n"); serial_write("\n");
if (regs != NULL) {
char hex_buf[17];
serial_write("Vector: 0x"); serial_write("Vector: 0x");
k_itoa_hex(regs->int_no, hex_buf); itoa_hex(regs->int_no, hex_buf);
serial_write(hex_buf); serial_write(hex_buf);
serial_write("\n"); serial_write("\n");
serial_write("Error Code: 0x"); serial_write("Error Code: 0x");
k_itoa_hex(regs->err_code, hex_buf); itoa_hex(regs->err_code, hex_buf);
serial_write(hex_buf); serial_write(hex_buf);
serial_write("\n"); serial_write("\n");
serial_write("RIP: 0x"); serial_write("RIP: 0x");
k_itoa_hex(regs->rip, hex_buf); itoa_hex(regs->rip, hex_buf);
serial_write(hex_buf); serial_write(hex_buf);
serial_write("\n"); serial_write("\n");
@ -133,13 +102,13 @@ void kernel_panic(registers_t *regs, const char *error_name) {
uint64_t cr2; uint64_t cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2)); asm volatile("mov %%cr2, %0" : "=r"(cr2));
serial_write("CR2: 0x"); serial_write("CR2: 0x");
k_itoa_hex(cr2, hex_buf); itoa_hex(cr2, hex_buf);
serial_write(hex_buf); serial_write(hex_buf);
serial_write("\n"); serial_write("\n");
} }
}
// Halt while (1) {
while(1) {
asm volatile("cli; hlt"); asm volatile("cli; hlt");
} }
} }

10
src/core/panic.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef PANIC_H
#define PANIC_H
#include "io.h"
#include "kutils.h"
#include "../sys/syscall.h"
void kernel_panic(registers_t *regs, const char *error_name);
#endif

View file

@ -105,48 +105,48 @@ void platform_get_cpu_flags(char *flags_str) {
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1)); asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
// ECX flags // ECX flags
if (ecx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "sse3 "); if (ecx & (1 << 0)) strcpy(flags_str + strlen(flags_str), "sse3 ");
if (ecx & (1 << 1)) k_strcpy(flags_str + k_strlen(flags_str), "pclmulqdq "); if (ecx & (1 << 1)) strcpy(flags_str + strlen(flags_str), "pclmulqdq ");
if (ecx & (1 << 3)) k_strcpy(flags_str + k_strlen(flags_str), "monitor "); if (ecx & (1 << 3)) strcpy(flags_str + strlen(flags_str), "monitor ");
if (ecx & (1 << 6)) k_strcpy(flags_str + k_strlen(flags_str), "ssse3 "); if (ecx & (1 << 6)) strcpy(flags_str + strlen(flags_str), "ssse3 ");
if (ecx & (1 << 9)) k_strcpy(flags_str + k_strlen(flags_str), "sdbg "); if (ecx & (1 << 9)) strcpy(flags_str + strlen(flags_str), "sdbg ");
if (ecx & (1 << 12)) k_strcpy(flags_str + k_strlen(flags_str), "fma "); if (ecx & (1 << 12)) strcpy(flags_str + strlen(flags_str), "fma ");
if (ecx & (1 << 13)) k_strcpy(flags_str + k_strlen(flags_str), "cx16 "); if (ecx & (1 << 13)) strcpy(flags_str + strlen(flags_str), "cx16 ");
if (ecx & (1 << 19)) k_strcpy(flags_str + k_strlen(flags_str), "sse4_1 "); if (ecx & (1 << 19)) strcpy(flags_str + strlen(flags_str), "sse4_1 ");
if (ecx & (1 << 20)) k_strcpy(flags_str + k_strlen(flags_str), "sse4_2 "); if (ecx & (1 << 20)) strcpy(flags_str + strlen(flags_str), "sse4_2 ");
if (ecx & (1 << 23)) k_strcpy(flags_str + k_strlen(flags_str), "popcnt "); if (ecx & (1 << 23)) strcpy(flags_str + strlen(flags_str), "popcnt ");
if (ecx & (1 << 25)) k_strcpy(flags_str + k_strlen(flags_str), "aes "); if (ecx & (1 << 25)) strcpy(flags_str + strlen(flags_str), "aes ");
if (ecx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "xsave "); if (ecx & (1 << 26)) strcpy(flags_str + strlen(flags_str), "xsave ");
if (ecx & (1 << 28)) k_strcpy(flags_str + k_strlen(flags_str), "avx "); if (ecx & (1 << 28)) strcpy(flags_str + strlen(flags_str), "avx ");
// EDX flags // EDX flags
if (edx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "fpu "); if (edx & (1 << 0)) strcpy(flags_str + strlen(flags_str), "fpu ");
if (edx & (1 << 3)) k_strcpy(flags_str + k_strlen(flags_str), "pse "); if (edx & (1 << 3)) strcpy(flags_str + strlen(flags_str), "pse ");
if (edx & (1 << 4)) k_strcpy(flags_str + k_strlen(flags_str), "tsc "); if (edx & (1 << 4)) strcpy(flags_str + strlen(flags_str), "tsc ");
if (edx & (1 << 6)) k_strcpy(flags_str + k_strlen(flags_str), "pae "); if (edx & (1 << 6)) strcpy(flags_str + strlen(flags_str), "pae ");
if (edx & (1 << 8)) k_strcpy(flags_str + k_strlen(flags_str), "cx8 "); if (edx & (1 << 8)) strcpy(flags_str + strlen(flags_str), "cx8 ");
if (edx & (1 << 9)) k_strcpy(flags_str + k_strlen(flags_str), "apic "); if (edx & (1 << 9)) strcpy(flags_str + strlen(flags_str), "apic ");
if (edx & (1 << 11)) k_strcpy(flags_str + k_strlen(flags_str), "sep "); if (edx & (1 << 11)) strcpy(flags_str + strlen(flags_str), "sep ");
if (edx & (1 << 15)) k_strcpy(flags_str + k_strlen(flags_str), "cmov "); if (edx & (1 << 15)) strcpy(flags_str + strlen(flags_str), "cmov ");
if (edx & (1 << 23)) k_strcpy(flags_str + k_strlen(flags_str), "mmx "); if (edx & (1 << 23)) strcpy(flags_str + strlen(flags_str), "mmx ");
if (edx & (1 << 24)) k_strcpy(flags_str + k_strlen(flags_str), "fxsr "); if (edx & (1 << 24)) strcpy(flags_str + strlen(flags_str), "fxsr ");
if (edx & (1 << 25)) k_strcpy(flags_str + k_strlen(flags_str), "sse "); if (edx & (1 << 25)) strcpy(flags_str + strlen(flags_str), "sse ");
if (edx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "sse2 "); if (edx & (1 << 26)) strcpy(flags_str + strlen(flags_str), "sse2 ");
// Extended leaf 0x80000001 for advanced flags // Extended leaf 0x80000001 for advanced flags
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0x80000001)); 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 << 11)) strcpy(flags_str + strlen(flags_str), "syscall ");
if (edx & (1 << 20)) k_strcpy(flags_str + k_strlen(flags_str), "nx "); if (edx & (1 << 20)) strcpy(flags_str + strlen(flags_str), "nx ");
if (edx & (1 << 26)) k_strcpy(flags_str + k_strlen(flags_str), "pdpe1gb "); if (edx & (1 << 26)) strcpy(flags_str + strlen(flags_str), "pdpe1gb ");
if (edx & (1 << 27)) k_strcpy(flags_str + k_strlen(flags_str), "rdtscp "); if (edx & (1 << 27)) strcpy(flags_str + strlen(flags_str), "rdtscp ");
if (edx & (1 << 29)) k_strcpy(flags_str + k_strlen(flags_str), "lm "); if (edx & (1 << 29)) strcpy(flags_str + strlen(flags_str), "lm ");
if (ecx & (1 << 0)) k_strcpy(flags_str + k_strlen(flags_str), "lahf_lm "); if (ecx & (1 << 0)) strcpy(flags_str + strlen(flags_str), "lahf_lm ");
if (ecx & (1 << 5)) k_strcpy(flags_str + k_strlen(flags_str), "abm "); if (ecx & (1 << 5)) strcpy(flags_str + strlen(flags_str), "abm ");
// Remove trailing space // Remove trailing space
int len = k_strlen(flags_str); int len = strlen(flags_str);
if (len > 0 && flags_str[len-1] == ' ') { if (len > 0 && flags_str[len-1] == ' ') {
flags_str[len-1] = '\0'; flags_str[len-1] = '\0';
} }

View file

@ -13,10 +13,10 @@ void get_os_info(os_info_t *info) {
for (size_t i = 0; i < sizeof(os_info_t); i++) p[i] = 0; for (size_t i = 0; i < sizeof(os_info_t); i++) p[i] = 0;
const char *os_name = "BoredOS"; const char *os_name = "BoredOS";
const char *os_version = "26.5-dev"; const char *os_version = "26.5.1-dev";
const char *os_codename = "Genesis"; const char *os_codename = "Genesis";
const char *kernel_name = "Boredkernel"; const char *kernel_name = "Boredkernel";
const char *kernel_version = "4.2.0-dev"; const char *kernel_version = "4.2.1-dev";
const char *build_date = __DATE__; const char *build_date = __DATE__;
const char *build_time = __TIME__; const char *build_time = __TIME__;
const char *build_arch = "x86_64"; const char *build_arch = "x86_64";

View file

@ -46,10 +46,9 @@ static void ahci_strcpy(char *d, const char *s) {
// Kernel virtual to physical address conversion // Kernel virtual to physical address conversion
extern uint64_t v2p(uint64_t vaddr); extern uint64_t v2p(uint64_t vaddr);
extern uint64_t p2v(uint64_t paddr);
// ============================================================================ static int ahci_disk_sync(Disk *disk);
// Port Setup static int ahci_find_free_slot(HBA_PORT *port);
// ============================================================================
static void ahci_stop_cmd(HBA_PORT *port) { static void ahci_stop_cmd(HBA_PORT *port) {
// Clear ST (Start) // Clear ST (Start)
@ -116,17 +115,17 @@ static void ahci_port_rebase(ahci_port_state_t *ps) {
port->fb = (uint32_t)(fb_phys & 0xFFFFFFFF); port->fb = (uint32_t)(fb_phys & 0xFFFFFFFF);
port->fbu = (uint32_t)(fb_phys >> 32); 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) + 32 * sizeof(HBA_PRDT_ENTRY);
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); ps->cmd_tbl = (HBA_CMD_TBL*)kmalloc_aligned(cmd_tbl_size, 256);
if (!ps->cmd_tbl) return; if (!ps->cmd_tbl) return;
mem_memset(ps->cmd_tbl, 0, cmd_tbl_size); 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); uint64_t ctba_phys = v2p((uint64_t)ps->cmd_tbl);
ps->cmd_list[0].ctba = (uint32_t)(ctba_phys & 0xFFFFFFFF); for (int i = 0; i < 32; i++) {
ps->cmd_list[0].ctbau = (uint32_t)(ctba_phys >> 32); ps->cmd_list[i].ctba = (uint32_t)(ctba_phys & 0xFFFFFFFF);
ps->cmd_list[0].prdtl = 1; // 1 PRDT entry default ps->cmd_list[i].ctbau = (uint32_t)(ctba_phys >> 32);
ps->cmd_list[i].prdtl = 1;
}
// Clear error and interrupt status // Clear error and interrupt status
port->serr = 0xFFFFFFFF; port->serr = 0xFFFFFFFF;
@ -135,10 +134,6 @@ static void ahci_port_rebase(ahci_port_state_t *ps) {
ahci_start_cmd(port); ahci_start_cmd(port);
} }
// ============================================================================
// Sector I/O
// ============================================================================
static int ahci_find_free_slot(HBA_PORT *port) { static int ahci_find_free_slot(HBA_PORT *port) {
uint32_t slots = (port->sact | port->ci); uint32_t slots = (port->sact | port->ci);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
@ -147,6 +142,76 @@ static int ahci_find_free_slot(HBA_PORT *port) {
return -1; return -1;
} }
static int ahci_identify(int port_num, uint32_t *sectors, char *model) {
ahci_port_state_t *ps = &ports[port_num];
HBA_PORT *port = ps->port;
uint64_t rflags = spinlock_acquire_irqsave(&ps->lock);
port->is = 0xFFFFFFFF;
int slot = ahci_find_free_slot(port);
if (slot < 0) { spinlock_release_irqrestore(&ps->lock, rflags); 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;
cmd_hdr->prdtl = 1;
HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl;
mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY));
uint16_t *buf = (uint16_t*)kmalloc_aligned(512, 512);
uint64_t phys = v2p((uint64_t)buf);
cmd_tbl->prdt[0].dba = (uint32_t)(phys & 0xFFFFFFFF);
cmd_tbl->prdt[0].dbau = (uint32_t)(phys >> 32);
cmd_tbl->prdt[0].dbc = 511; // 512 bytes
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; // Command
fis->command = 0xEC; // IDENTIFY DEVICE
// Wait for port to be idle
int timeout = 1000000;
while ((port->tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && --timeout > 0);
if (timeout <= 0) { kfree(buf); spinlock_release_irqrestore(&ps->lock, rflags); return -1; }
port->ci = (1 << slot);
while (1) {
if ((port->ci & (1 << slot)) == 0) break;
if (port->is & HBA_PORT_IS_TFES) { kfree(buf); spinlock_release_irqrestore(&ps->lock, rflags); return -1; }
}
// Extract sectors (28-bit LBA for now, or 48-bit if supported)
uint32_t s28 = *((uint32_t*)&buf[60]);
uint64_t s48 = *((uint64_t*)&buf[100]);
if (s48 > 0) *sectors = (uint32_t)s48;
else *sectors = s28;
// Extract model name (Words 27-46, 40 bytes, big-endian shorts)
for (int i = 0; i < 20; i++) {
model[i*2] = (char)(buf[27+i] >> 8);
model[i*2+1] = (char)(buf[27+i] & 0xFF);
}
model[40] = 0;
// Swap bytes in model string (ATA strings are byte-swapped)
for (int i = 0; i < 40; i += 2) {
char tmp = model[i];
model[i] = model[i+1];
model[i+1] = tmp;
}
kfree(buf);
spinlock_release_irqrestore(&ps->lock, rflags);
return 0;
}
int ahci_read_sectors(int port_num, uint64_t lba, uint32_t count, uint8_t *buffer) { 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; if (!ahci_initialized || port_num < 0 || port_num >= MAX_AHCI_PORTS) return -1;
ahci_port_state_t *ps = &ports[port_num]; ahci_port_state_t *ps = &ports[port_num];
@ -167,14 +232,38 @@ int ahci_read_sectors(int port_num, uint64_t lba, uint32_t count, uint8_t *buffe
cmd_hdr->prdtl = 1; cmd_hdr->prdtl = 1;
HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl; HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl;
mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + sizeof(HBA_PRDT_ENTRY)); mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY));
// Setup PRDT extern uint64_t paging_get_pml4_phys(void);
uint64_t buf_phys = v2p((uint64_t)buffer); extern uint64_t paging_virt2phys(uint64_t pml4_phys, uint64_t virtual_addr);
cmd_tbl->prdt[0].dba = (uint32_t)(buf_phys & 0xFFFFFFFF); uint64_t pml4 = paging_get_pml4_phys();
cmd_tbl->prdt[0].dbau = (uint32_t)(buf_phys >> 32); uint64_t buf_addr = (uint64_t)buffer;
cmd_tbl->prdt[0].dbc = (count * 512) - 1; // 0-based byte count uint32_t remaining = count * 512;
cmd_tbl->prdt[0].i = 1; int prd_idx = 0;
while (remaining > 0 && prd_idx < 32) {
uint64_t phys = paging_virt2phys(pml4, buf_addr);
if (!phys) {
spinlock_release_irqrestore(&ps->lock, rflags);
return -1;
}
uint32_t offset = buf_addr & 0xFFF;
uint32_t can_do = 4096 - offset;
if (can_do > remaining) can_do = remaining;
cmd_tbl->prdt[prd_idx].dba = (uint32_t)(phys & 0xFFFFFFFF);
cmd_tbl->prdt[prd_idx].dbau = (uint32_t)(phys >> 32);
cmd_tbl->prdt[prd_idx].dbc = can_do - 1; // 0-based
cmd_tbl->prdt[prd_idx].i = 0;
buf_addr += can_do;
remaining -= can_do;
prd_idx++;
}
if (prd_idx > 0) cmd_tbl->prdt[prd_idx - 1].i = 1; // Interrupt on last
cmd_hdr->prdtl = prd_idx;
// Setup Command FIS // Setup Command FIS
FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis; FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis;
@ -238,13 +327,39 @@ int ahci_write_sectors(int port_num, uint64_t lba, uint32_t count, const uint8_t
cmd_hdr->prdtl = 1; cmd_hdr->prdtl = 1;
HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl; HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl;
mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + sizeof(HBA_PRDT_ENTRY)); mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY));
uint64_t buf_phys = v2p((uint64_t)buffer); // Setup PRDT - handle buffers spanning multiple physical pages
cmd_tbl->prdt[0].dba = (uint32_t)(buf_phys & 0xFFFFFFFF); extern uint64_t paging_get_pml4_phys(void);
cmd_tbl->prdt[0].dbau = (uint32_t)(buf_phys >> 32); extern uint64_t paging_virt2phys(uint64_t pml4_phys, uint64_t virtual_addr);
cmd_tbl->prdt[0].dbc = (count * 512) - 1; uint64_t pml4 = paging_get_pml4_phys();
cmd_tbl->prdt[0].i = 1; uint64_t buf_addr = (uint64_t)buffer;
uint32_t remaining = count * 512;
int prd_idx = 0;
while (remaining > 0 && prd_idx < 32) {
uint64_t phys = paging_virt2phys(pml4, buf_addr);
if (!phys) {
spinlock_release_irqrestore(&ps->lock, rflags);
return -1;
}
uint32_t offset = buf_addr & 0xFFF;
uint32_t can_do = 4096 - offset;
if (can_do > remaining) can_do = remaining;
cmd_tbl->prdt[prd_idx].dba = (uint32_t)(phys & 0xFFFFFFFF);
cmd_tbl->prdt[prd_idx].dbau = (uint32_t)(phys >> 32);
cmd_tbl->prdt[prd_idx].dbc = can_do - 1; // 0-based
cmd_tbl->prdt[prd_idx].i = 0;
buf_addr += can_do;
remaining -= can_do;
prd_idx++;
}
if (prd_idx > 0) cmd_tbl->prdt[prd_idx - 1].i = 1; // Interrupt on last
cmd_hdr->prdtl = prd_idx;
FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis; FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis;
fis->fis_type = FIS_TYPE_REG_H2D; fis->fis_type = FIS_TYPE_REG_H2D;
@ -321,6 +436,24 @@ static int ahci_disk_write_sector(Disk *disk, uint32_t sector, const uint8_t *bu
return ahci_write_sectors(data->ahci_port, (uint64_t)sector, 1, buffer); return ahci_write_sectors(data->ahci_port, (uint64_t)sector, 1, buffer);
} }
static int ahci_disk_read_sectors(Disk *disk, uint32_t sector, uint32_t count, uint8_t *buffer) {
AHCIDriverData *data = (AHCIDriverData*)disk->driver_data;
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, count, buffer);
}
return ahci_read_sectors(data->ahci_port, (uint64_t)sector, count, buffer);
}
static int ahci_disk_write_sectors(Disk *disk, uint32_t sector, uint32_t count, 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, count, buffer);
}
return ahci_write_sectors(data->ahci_port, (uint64_t)sector, count, buffer);
}
// ============================================================================ // ============================================================================
// Initialization // Initialization
// ============================================================================ // ============================================================================
@ -368,9 +501,7 @@ void ahci_init(void) {
serial_write_hex((uint32_t)abar_phys); serial_write_hex((uint32_t)abar_phys);
serial_write("\n"); serial_write("\n");
// Map ABAR region into kernel virtual address space uint64_t abar_virt = p2v(abar_phys);
// 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) { for (uint64_t offset = 0; offset < 0x2000; offset += 4096) {
paging_map_page(paging_get_pml4_phys(), abar_virt + offset, paging_map_page(paging_get_pml4_phys(), abar_virt + offset,
abar_phys + offset, abar_phys + offset,
@ -409,6 +540,7 @@ void ahci_init(void) {
ahci_port_rebase(&ports[i]); ahci_port_rebase(&ports[i]);
ports[i].active = true; ports[i].active = true;
active_port_count++; active_port_count++;
ahci_initialized = true;
// Register as a block device // Register as a block device
Disk *disk = (Disk*)kmalloc(sizeof(Disk)); Disk *disk = (Disk*)kmalloc(sizeof(Disk));
@ -418,85 +550,36 @@ void ahci_init(void) {
disk->devname[0] = 0; // Auto-assign disk->devname[0] = 0; // Auto-assign
disk->type = DISK_TYPE_SATA; disk->type = DISK_TYPE_SATA;
uint32_t sectors = 0;
char model[64];
if (ahci_identify(i, &sectors, model) == 0) {
ahci_strcpy(disk->label, model);
disk->total_sectors = sectors;
} else {
ahci_strcpy(disk->label, "SATA Drive"); ahci_strcpy(disk->label, "SATA Drive");
disk->total_sectors = 0;
}
disk->read_sector = ahci_disk_read_sector; disk->read_sector = ahci_disk_read_sector;
disk->write_sector = ahci_disk_write_sector; disk->write_sector = ahci_disk_write_sector;
disk->read_sectors = ahci_disk_read_sectors;
disk->write_sectors = ahci_disk_write_sectors;
disk->sync = ahci_disk_sync;
disk->driver_data = drv; disk->driver_data = drv;
disk->partition_lba_offset = 0; disk->partition_lba_offset = 0;
disk->total_sectors = 0;
disk->parent = NULL; disk->parent = NULL;
disk->is_partition = false; disk->is_partition = false;
disk->is_fat32 = false; disk->is_fat32 = false;
disk_register(disk); 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); extern void serial_write(const char *str);
extern int disk_rescan(Disk *disk);
serial_write("[AHCI] Probing partitions on /dev/"); serial_write("[AHCI] Probing partitions on /dev/");
serial_write(disk->devname); serial_write(disk->devname);
serial_write("...\n"); serial_write("...\n");
disk_rescan(disk);
// 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) { } else if (type == 1) {
serial_write("[AHCI] Port "); serial_write("[AHCI] Port ");
@ -514,3 +597,52 @@ void ahci_init(void) {
serial_write("[AHCI] No active SATA ports found\n"); serial_write("[AHCI] No active SATA ports found\n");
} }
} }
int ahci_flush_cache(int port_num) {
HBA_PORT *port = &abar->ports[port_num];
ahci_port_state_t *ps = &ports[port_num];
if (port_num < 0 || port_num >= 32 || !ps->active) return -1;
uint64_t rflags = spinlock_acquire_irqsave(&ps->lock);
port->is = 0xFFFFFFFF; // Clear interrupts
int slot = ahci_find_free_slot(port);
if (slot == -1) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; }
HBA_CMD_HEADER *cmd_header = (HBA_CMD_HEADER*)p2v(port->clb);
cmd_header += slot;
cmd_header->cfl = sizeof(FIS_REG_H2D) / 4;
cmd_header->w = 0;
cmd_header->prdtl = 0;
HBA_CMD_TBL *cmd_tbl = (HBA_CMD_TBL*)p2v(cmd_header->ctba);
for (int i = 0; i < 256; i++) ((uint8_t*)cmd_tbl)[i] = 0;
FIS_REG_H2D *fis = (FIS_REG_H2D*)(&cmd_tbl->cfis);
fis->fis_type = FIS_TYPE_REG_H2D;
fis->c = 1;
fis->command = 0xEA; // FLUSH CACHE EXT
// Wait for port to be ready
int timeout = 1000000;
while ((port->tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && --timeout > 0);
if (timeout == 0) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; }
port->ci = (1 << slot);
while (1) {
if ((port->ci & (1 << slot)) == 0) break;
if (port->is & HBA_PORT_IS_TFES) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; }
}
spinlock_release_irqrestore(&ps->lock, rflags);
return 0;
}
static int ahci_disk_sync(Disk *disk) {
AHCIDriverData *drv = (AHCIDriverData*)disk->driver_data;
if (!drv) return -1;
return ahci_flush_cache(drv->ahci_port);
}

View file

@ -152,6 +152,12 @@ typedef struct {
#define HBA_PORT_CMD_FRE 0x0010 // FIS Receive Enable #define HBA_PORT_CMD_FRE 0x0010 // FIS Receive Enable
#define HBA_PORT_CMD_FR 0x4000 // FIS Receive Running #define HBA_PORT_CMD_FR 0x4000 // FIS Receive Running
#define HBA_PORT_CMD_CR 0x8000 // Command List Running #define HBA_PORT_CMD_CR 0x8000 // Command List Running
#define HBA_PORT_IS_TFES (1u << 30)
#define ATA_SR_BSY 0x80
#define ATA_SR_DRDY 0x40
#define ATA_SR_DRQ 0x08
#define ATA_SR_ERR 0x01
// ============================================================================ // ============================================================================
// ATA Commands // ATA Commands

View file

@ -18,16 +18,18 @@ typedef enum {
} DiskType; } DiskType;
typedef struct Disk { typedef struct Disk {
char devname[16]; // Device name: "sda", "sdb", "sda1", etc. char devname[16];
DiskType type; DiskType type;
bool is_fat32; bool is_fat32;
char label[32]; // Human-readable label bool is_esp;
uint32_t partition_lba_offset; // LBA offset of partition (0 for whole disk) char label[32];
uint32_t total_sectors; // Total sectors on this device/partition uint32_t partition_lba_offset;
uint32_t total_sectors;
// Function pointers for driver operations
int (*read_sector)(struct Disk *disk, uint32_t sector, uint8_t *buffer); int (*read_sector)(struct Disk *disk, uint32_t sector, uint8_t *buffer);
int (*write_sector)(struct Disk *disk, uint32_t sector, const uint8_t *buffer); int (*write_sector)(struct Disk *disk, uint32_t sector, const uint8_t *buffer);
int (*read_sectors)(struct Disk *disk, uint32_t sector, uint32_t count, uint8_t *buffer);
int (*write_sectors)(struct Disk *disk, uint32_t sector, uint32_t count, const uint8_t *buffer);
int (*sync)(struct Disk *disk);
// Private driver data // Private driver data
void *driver_data; void *driver_data;
@ -38,6 +40,17 @@ typedef struct Disk {
bool registered; bool registered;
} Disk; } Disk;
typedef struct {
uint32_t lba_start;
uint32_t sector_count;
uint8_t part_type;
uint8_t flags;
char label[36];
} disk_partition_spec_t;
#define PART_FLAG_ESP 0x01
#define MIN_INSTALL_SECTORS 2097152
// Initialization and scanning // Initialization and scanning
void disk_manager_init(void); void disk_manager_init(void);
void disk_manager_scan(void); void disk_manager_scan(void);
@ -45,7 +58,7 @@ void disk_manager_scan(void);
// Device registration // Device registration
void disk_register(Disk *disk); void disk_register(Disk *disk);
void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count, void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count,
bool is_fat32, int part_num); bool is_fat32, bool is_esp, int part_num);
// Lookup // Lookup
Disk* disk_get_by_name(const char *devname); Disk* disk_get_by_name(const char *devname);
@ -59,4 +72,11 @@ const char* disk_get_next_dev_name(void); // Returns "sda", "sdb", etc.
Disk* disk_get_by_letter(char letter); Disk* disk_get_by_letter(char letter);
char disk_get_next_free_letter(void); char disk_get_next_free_letter(void);
int disk_write_gpt(Disk *disk, disk_partition_spec_t *parts, int count);
int disk_write_mbr(Disk *disk, disk_partition_spec_t *parts, int count);
int disk_sync(Disk *disk);
int disk_rescan(Disk *disk);
#endif #endif

View file

@ -42,6 +42,32 @@ static int dm_strlen(const char *s) {
return n; return n;
} }
static void dm_copy_fat_label(char *dst, const uint8_t *src) {
int end = 11;
while (end > 0 && src[end - 1] == ' ') end--;
for (int i = 0; i < end && i < 31; i++) dst[i] = (char)src[i];
dst[end < 31 ? end : 31] = 0;
}
static void disk_load_fat32_label(Disk *disk) {
uint8_t *buffer;
FAT32_BootSector *bpb;
char label[32];
if (!disk || !disk->read_sector) return;
buffer = (uint8_t*)kmalloc(512);
if (!buffer) return;
if (disk->read_sector(disk, 0, buffer) == 0 && buffer[510] == 0x55 && buffer[511] == 0xAA) {
bpb = (FAT32_BootSector*)buffer;
dm_copy_fat_label(label, bpb->volume_label);
if (label[0]) dm_strcpy(disk->label, label);
}
kfree(buffer);
}
// === ATA Definitions (Legacy IDE PIO — kept as fallback) === // === ATA Definitions (Legacy IDE PIO — kept as fallback) ===
#define ATA_PRIMARY_IO 0x1F0 #define ATA_PRIMARY_IO 0x1F0
@ -227,6 +253,105 @@ static int ata_write_sector(Disk *disk, uint32_t lba, const uint8_t *buffer) {
return 0; return 0;
} }
static int ata_read_sectors(Disk *disk, uint32_t lba, uint32_t count, uint8_t *buffer) {
ATADriverData *data = (ATADriverData*)disk->driver_data;
uint16_t port_base = data->port_base;
bool slave = data->slave;
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);
while (count > 0) {
uint8_t batch = (count > 255) ? 255 : (uint8_t)count;
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_SEC_COUNT0, batch);
outb(port_base + ATA_REG_LBA0, (uint8_t)(lba));
outb(port_base + ATA_REG_LBA1, (uint8_t)(lba >> 8));
outb(port_base + ATA_REG_LBA2, (uint8_t)(lba >> 16));
outb(port_base + ATA_REG_COMMAND, ATA_CMD_READ_PIO);
for (uint8_t b = 0; b < batch; b++) {
if (ata_wait_bsy(port_base) != 0 || ata_wait_drq(port_base) != 0) {
spinlock_release_irqrestore(&ide_lock, flags);
return -1;
}
uint16_t *pptr = (uint16_t*)(buffer + (b * 512));
for (int i = 0; i < 256; i++) {
pptr[i] = inw(port_base + ATA_REG_DATA);
}
}
lba += batch;
buffer += batch * 512;
count -= batch;
}
spinlock_release_irqrestore(&ide_lock, flags);
return 0;
}
static int ata_write_sectors(Disk *disk, uint32_t lba, uint32_t count, const uint8_t *buffer) {
ATADriverData *data = (ATADriverData*)disk->driver_data;
uint16_t port_base = data->port_base;
bool slave = data->slave;
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);
while (count > 0) {
uint8_t batch = (count > 255) ? 255 : (uint8_t)count;
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_SEC_COUNT0, batch);
outb(port_base + ATA_REG_LBA0, (uint8_t)(lba));
outb(port_base + ATA_REG_LBA1, (uint8_t)(lba >> 8));
outb(port_base + ATA_REG_LBA2, (uint8_t)(lba >> 16));
outb(port_base + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO);
for (uint8_t b = 0; b < batch; b++) {
if (ata_wait_bsy(port_base) != 0 || ata_wait_drq(port_base) != 0) {
spinlock_release_irqrestore(&ide_lock, flags);
return -1;
}
const uint16_t *pptr = (const uint16_t*)(buffer + (b * 512));
for (int i = 0; i < 256; i++) {
outw(port_base + ATA_REG_DATA, pptr[i]);
}
}
outb(port_base + ATA_REG_COMMAND, 0xE7); // Cache Flush
if (ata_wait_bsy(port_base) != 0) {
spinlock_release_irqrestore(&ide_lock, flags);
return -1;
}
lba += batch;
buffer += batch * 512;
count -= batch;
}
spinlock_release_irqrestore(&ide_lock, flags);
return 0;
}
// === Device Naming === // === Device Naming ===
const char* disk_get_next_dev_name(void) { const char* disk_get_next_dev_name(void) {
@ -257,11 +382,13 @@ void disk_register(Disk *disk) {
serial_write(disk->devname); serial_write(disk->devname);
serial_write(" ("); serial_write(" (");
serial_write(disk->label); serial_write(disk->label);
serial_write(")\n"); serial_write(") size=");
serial_write_num(disk->total_sectors);
serial_write(" sectors\n");
} }
void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count, void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_count,
bool is_fat32, int part_num) { bool is_fat32, bool is_esp, int part_num) {
if (disk_count >= MAX_DISKS) return; if (disk_count >= MAX_DISKS) return;
Disk *part = (Disk*)kmalloc(sizeof(Disk)); Disk *part = (Disk*)kmalloc(sizeof(Disk));
@ -275,7 +402,8 @@ void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_
part->type = parent->type; part->type = parent->type;
part->is_fat32 = is_fat32; part->is_fat32 = is_fat32;
dm_strcpy(part->label, is_fat32 ? "FAT32 Partition" : "Unknown Partition"); part->is_esp = is_esp;
dm_strcpy(part->label, is_esp ? "EFI System Partition" : (is_fat32 ? "FAT32 Partition" : "Unknown Partition"));
part->partition_lba_offset = lba_offset; part->partition_lba_offset = lba_offset;
part->total_sectors = sector_count; part->total_sectors = sector_count;
part->read_sector = parent->read_sector; part->read_sector = parent->read_sector;
@ -285,6 +413,8 @@ void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_
part->is_partition = true; part->is_partition = true;
part->registered = true; part->registered = true;
if (is_fat32) disk_load_fat32_label(part);
disks[disk_count++] = part; disks[disk_count++] = part;
serial_write("[DISK] Registered /dev/"); serial_write("[DISK] Registered /dev/");
@ -294,8 +424,8 @@ void disk_register_partition(Disk *parent, uint32_t lba_offset, uint32_t sector_
serial_write(", "); serial_write(", ");
serial_write_num(sector_count); serial_write_num(sector_count);
serial_write(" sectors, FAT32="); serial_write(" sectors, FAT32=");
serial_write(" sectors, FAT32=");
serial_write(is_fat32 ? "yes" : "no"); serial_write(is_fat32 ? "yes" : "no");
if (is_esp) serial_write(", ESP=yes");
serial_write(")\n"); serial_write(")\n");
if (is_fat32) { if (is_fat32) {
@ -411,12 +541,18 @@ static void parse_mbr_partitions(Disk *disk) {
if (!buffer) return; if (!buffer) return;
if (disk->read_sector(disk, 0, buffer) != 0) { if (disk->read_sector(disk, 0, buffer) != 0) {
serial_write("[DISK] MBR read failed on /dev/");
serial_write(disk->devname);
serial_write("\n");
kfree(buffer); kfree(buffer);
return; return;
} }
// Check for valid MBR signature // Check for valid MBR signature
if (buffer[510] != 0x55 || buffer[511] != 0xAA) { if (buffer[510] != 0x55 || buffer[511] != 0xAA) {
serial_write("[DISK] Invalid MBR signature on /dev/");
serial_write(disk->devname);
serial_write("\n");
kfree(buffer); kfree(buffer);
return; return;
} }
@ -424,6 +560,7 @@ static void parse_mbr_partitions(Disk *disk) {
MBR_PartitionEntry *partitions = (MBR_PartitionEntry*)&buffer[446]; MBR_PartitionEntry *partitions = (MBR_PartitionEntry*)&buffer[446];
int part_num = 1; int part_num = 1;
int part_count = 0;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
uint32_t start = partitions[i].lba_start; uint32_t start = partitions[i].lba_start;
uint32_t size = partitions[i].sector_count; uint32_t size = partitions[i].sector_count;
@ -446,7 +583,8 @@ static void parse_mbr_partitions(Disk *disk) {
} }
disk_register_partition(disk, partitions[i].lba_start, disk_register_partition(disk, partitions[i].lba_start,
partitions[i].sector_count, fat32, part_num); partitions[i].sector_count, fat32, false, part_num);
part_count++;
part_num++; part_num++;
} }
@ -457,6 +595,11 @@ static void parse_mbr_partitions(Disk *disk) {
serial_write("\n"); serial_write("\n");
disk->is_fat32 = true; disk->is_fat32 = true;
disk->partition_lba_offset = 0; disk->partition_lba_offset = 0;
disk_load_fat32_label(disk);
} else if (part_count == 0) {
serial_write("[DISK] No MBR partitions found on /dev/");
serial_write(disk->devname);
serial_write("\n");
} }
kfree(buffer); kfree(buffer);
@ -479,6 +622,8 @@ static void try_add_ata_drive(uint16_t port, bool slave, const char *name) {
dm_strcpy(new_disk->label, name); dm_strcpy(new_disk->label, name);
new_disk->read_sector = ata_read_sector; new_disk->read_sector = ata_read_sector;
new_disk->write_sector = ata_write_sector; new_disk->write_sector = ata_write_sector;
new_disk->read_sectors = ata_read_sectors;
new_disk->write_sectors = ata_write_sectors;
new_disk->driver_data = data; new_disk->driver_data = data;
new_disk->partition_lba_offset = 0; new_disk->partition_lba_offset = 0;
new_disk->total_sectors = sectors; new_disk->total_sectors = sectors;
@ -504,8 +649,6 @@ void disk_manager_init(void) {
next_drive_letter_idx = 0; next_drive_letter_idx = 0;
log_ok("Disk manager ready"); 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) { void disk_manager_scan(void) {
@ -523,3 +666,407 @@ void disk_manager_scan(void) {
log_ok("AHCI ports initialized, skipping IDE"); log_ok("AHCI ports initialized, skipping IDE");
} }
} }
static uint32_t crc32_compute(const uint8_t *data, uint32_t len) {
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++)
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
}
return ~crc;
}
#define GPT_PART_ENTRY_COUNT 128
#define GPT_PART_ENTRY_SIZE 128
_Static_assert(GPT_PART_ENTRY_COUNT * GPT_PART_ENTRY_SIZE == 32 * 512,
"GPT partition array must be exactly 32 sectors");
static const uint8_t GPT_GUID_ESP[16] = {
0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11,
0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B
};
static const uint8_t GPT_GUID_BASIC_DATA[16] = {
0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44,
0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7
};
typedef struct __attribute__((packed)) {
uint64_t signature;
uint32_t revision;
uint32_t header_size;
uint32_t crc32;
uint32_t reserved;
uint64_t my_lba;
uint64_t alternate_lba;
uint64_t first_usable_lba;
uint64_t last_usable_lba;
uint8_t disk_guid[16];
uint64_t partition_entry_lba;
uint32_t num_partition_entries;
uint32_t size_of_partition_entry;
uint32_t partition_entry_array_crc32;
} GPT_Header;
typedef struct __attribute__((packed)) {
uint8_t type_guid[16];
uint8_t partition_guid[16];
uint64_t start_lba;
uint64_t end_lba;
uint64_t attributes;
uint16_t name[36];
} GPT_Entry;
static void gpt_make_pseudo_guid(uint8_t *guid, const char *label, uint32_t total_sectors) {
uint32_t h = 5381;
for (int i = 0; label[i]; i++)
h = h * 33 + (unsigned char)label[i];
h ^= total_sectors;
for (int i = 0; i < 16; i++)
guid[i] = (uint8_t)(h >> ((i % 4) * 8));
guid[8] = (guid[8] & 0x3F) | 0x80;
guid[6] = (guid[6] & 0x0F) | 0x40;
}
int disk_write_gpt(Disk *disk, disk_partition_spec_t *parts, int count) {
if (!disk || !parts || count <= 0 || count > GPT_PART_ENTRY_COUNT)
return -1;
uint32_t first_usable = 2048;
uint32_t last_usable = disk->total_sectors - 34;
for (int i = 0; i < count; i++) {
if (parts[i].sector_count == 0) {
serial_write("[GPT] Error: zero-sized partition\n");
return -1;
}
uint32_t start = parts[i].lba_start;
uint32_t end = start + parts[i].sector_count - 1;
if (start % 2048 != 0) {
start = ((start + 2047) / 2048) * 2048;
parts[i].lba_start = start;
end = start + parts[i].sector_count - 1;
serial_write("[GPT] Warning: start rounded up to 2048 boundary\n");
}
if (start < first_usable || end > last_usable) {
serial_write("[GPT] Error: partition out of usable range\n");
return -1;
}
for (int j = 0; j < i; j++) {
uint32_t js = parts[j].lba_start;
uint32_t je = js + parts[j].sector_count - 1;
if (start <= je && end >= js) {
serial_write("[GPT] Error: overlapping partitions\n");
return -1;
}
}
}
uint8_t *entry_buf = (uint8_t *)kmalloc(GPT_PART_ENTRY_COUNT * GPT_PART_ENTRY_SIZE);
if (!entry_buf) return -1;
for (int i = 0; i < GPT_PART_ENTRY_COUNT * GPT_PART_ENTRY_SIZE; i++) entry_buf[i] = 0;
for (int i = 0; i < count; i++) {
GPT_Entry *e = (GPT_Entry *)(entry_buf + i * GPT_PART_ENTRY_SIZE);
if (parts[i].flags & PART_FLAG_ESP)
for (int b = 0; b < 16; b++) e->type_guid[b] = GPT_GUID_ESP[b];
else
for (int b = 0; b < 16; b++) e->type_guid[b] = GPT_GUID_BASIC_DATA[b];
gpt_make_pseudo_guid(e->partition_guid, parts[i].label, disk->total_sectors);
e->start_lba = parts[i].lba_start;
e->end_lba = parts[i].lba_start + parts[i].sector_count - 1;
e->attributes = (parts[i].flags & PART_FLAG_ESP) ? 0x01 : 0x00;
/* UTF-16LE name */
for (int c = 0; c < 36 && parts[i].label[c]; c++)
e->name[c] = (uint16_t)(unsigned char)parts[i].label[c];
}
uint32_t entry_crc = crc32_compute(entry_buf, GPT_PART_ENTRY_COUNT * GPT_PART_ENTRY_SIZE);
uint8_t *hdr_buf = (uint8_t *)kmalloc(512);
if (!hdr_buf) { kfree(entry_buf); return -1; }
for (int i = 0; i < 512; i++) hdr_buf[i] = 0;
GPT_Header *hdr = (GPT_Header *)hdr_buf;
hdr->signature = 0x5452415020494645ULL;
hdr->revision = 0x00010000;
hdr->header_size = 92;
hdr->crc32 = 0;
hdr->reserved = 0;
hdr->my_lba = 1;
hdr->alternate_lba = disk->total_sectors - 1;
hdr->first_usable_lba = first_usable;
hdr->last_usable_lba = last_usable;
gpt_make_pseudo_guid(hdr->disk_guid, disk->devname, disk->total_sectors);
hdr->partition_entry_lba = 2;
hdr->num_partition_entries = GPT_PART_ENTRY_COUNT;
hdr->size_of_partition_entry = GPT_PART_ENTRY_SIZE;
hdr->partition_entry_array_crc32 = entry_crc;
hdr->crc32 = 0;
hdr->crc32 = crc32_compute(hdr_buf, hdr->header_size);
uint8_t *mbr_buf = (uint8_t *)kmalloc(512);
if (!mbr_buf) { kfree(entry_buf); kfree(hdr_buf); return -1; }
for (int i = 0; i < 512; i++) mbr_buf[i] = 0;
mbr_buf[446] = 0x00; /* Status: Non-bootable */
mbr_buf[447] = 0x00; mbr_buf[448] = 0x02; mbr_buf[449] = 0x00; /* CHS Start: 0x000200 */
mbr_buf[450] = 0xEE; /* Type: GPT Protective */
mbr_buf[451] = 0xFF; mbr_buf[452] = 0xFF; mbr_buf[453] = 0xFF; /* CHS End: 0xFFFFFF */
mbr_buf[454] = 0x01; mbr_buf[455] = 0x00; mbr_buf[456] = 0x00; mbr_buf[457] = 0x00; /* LBA Start: 1 */
uint32_t pmbr_size = disk->total_sectors - 1;
mbr_buf[458] = (uint8_t)(pmbr_size);
mbr_buf[459] = (uint8_t)(pmbr_size >> 8);
mbr_buf[460] = (uint8_t)(pmbr_size >> 16);
mbr_buf[461] = (uint8_t)(pmbr_size >> 24);
mbr_buf[510] = 0x55;
mbr_buf[511] = 0xAA;
disk->write_sector(disk, 0, mbr_buf);
kfree(mbr_buf);
if (disk->write_sector(disk, 1, hdr_buf) != 0) {
serial_write("[GPT] Error: failed to write header\n");
kfree(entry_buf); kfree(hdr_buf); return -1;
}
for (int s = 0; s < 32; s++) {
if (disk->write_sector(disk, 2 + s, entry_buf + s * 512) != 0) {
serial_write("[GPT] Error: failed to write partition entries\n");
kfree(entry_buf); kfree(hdr_buf); return -1;
}
}
GPT_Header *bhdr = (GPT_Header *)hdr_buf;
for (int i = 0; i < 512; i++) hdr_buf[i] = 0;
bhdr->signature = 0x5452415020494645ULL;
bhdr->revision = 0x00010000;
bhdr->header_size = 92;
bhdr->my_lba = disk->total_sectors - 1;
bhdr->alternate_lba = 1;
bhdr->first_usable_lba = first_usable;
bhdr->last_usable_lba = last_usable;
gpt_make_pseudo_guid(bhdr->disk_guid, disk->devname, disk->total_sectors);
bhdr->partition_entry_lba = disk->total_sectors - 33;
bhdr->num_partition_entries = GPT_PART_ENTRY_COUNT;
bhdr->size_of_partition_entry = GPT_PART_ENTRY_SIZE;
bhdr->partition_entry_array_crc32 = entry_crc;
bhdr->crc32 = 0;
bhdr->crc32 = crc32_compute(hdr_buf, bhdr->header_size);
for (int s = 0; s < 32; s++) {
disk->write_sector(disk, disk->total_sectors - 33 + s, entry_buf + s * 512);
}
disk->write_sector(disk, disk->total_sectors - 1, hdr_buf);
kfree(entry_buf);
kfree(hdr_buf);
serial_write("[DISK] GPT written to /dev/");
serial_write(disk->devname);
serial_write("\n");
return 0;
}
int disk_write_mbr(Disk *disk, disk_partition_spec_t *parts, int count) {
if (!disk || !parts || count <= 0 || count > 4) return -1;
uint8_t *buf = (uint8_t *)kmalloc(512);
if (!buf) return -1;
for (int i = 0; i < 512; i++) buf[i] = 0;
for (int i = 0; i < count; i++) {
if (parts[i].sector_count == 0) { kfree(buf); return -1; }
uint8_t *entry = buf + 446 + i * 16;
entry[0] = 0x80;
entry[4] = 0x0C;
uint32_t lba_start = parts[i].lba_start;
uint32_t sec_count = parts[i].sector_count;
entry[8] = (uint8_t)(lba_start);
entry[9] = (uint8_t)(lba_start >> 8);
entry[10] = (uint8_t)(lba_start >> 16);
entry[11] = (uint8_t)(lba_start >> 24);
entry[12] = (uint8_t)(sec_count);
entry[13] = (uint8_t)(sec_count >> 8);
entry[14] = (uint8_t)(sec_count >> 16);
entry[15] = (uint8_t)(sec_count >> 24);
}
buf[510] = 0x55;
buf[511] = 0xAA;
int ret = disk->write_sector(disk, 0, buf);
kfree(buf);
if (ret == 0) {
serial_write("[DISK] MBR written to /dev/");
serial_write(disk->devname);
serial_write("\n");
}
return ret;
}
int disk_sync(Disk *disk) {
if (!disk) return -1;
Disk *target = disk->parent ? disk->parent : disk;
// Use device-specific sync if available
if (target->sync) return target->sync(target);
if (target->type == DISK_TYPE_IDE) {
ATADriverData *data = (ATADriverData *)target->driver_data;
if (!data) return -1;
uint64_t flags = spinlock_acquire_irqsave(&ide_lock);
if (ata_wait_bsy(data->port_base) == 0) {
outb(data->port_base + ATA_REG_HDDEVSEL, data->slave ? 0xB0 : 0xA0);
outb(data->port_base + ATA_REG_COMMAND, 0xE7);
ata_wait_bsy(data->port_base);
}
spinlock_release_irqrestore(&ide_lock, flags);
return 0;
}
return 0;
}
static void disk_remove_partitions(Disk *parent) {
for (int i = 0; i < disk_count; i++) {
if (disks[i] && disks[i]->parent == parent) {
Disk *p = disks[i];
// Unmount from VFS if it's mounted
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, p->devname);
extern bool vfs_umount(const char *mount_path);
vfs_umount(mount_path);
for (int j = i; j < disk_count - 1; j++) {
disks[j] = disks[j + 1];
}
disks[disk_count - 1] = NULL;
disk_count--;
i--;
kfree(p);
}
}
}
static void parse_gpt_partitions(Disk *disk) {
uint8_t *buffer = (uint8_t*)kmalloc(512);
if (!buffer) return;
if (disk->read_sector(disk, 1, buffer) != 0) {
serial_write("[DISK] GPT header read failed on /dev/");
serial_write(disk->devname);
serial_write("\n");
kfree(buffer);
return;
}
GPT_Header *hdr = (GPT_Header *)buffer;
if (hdr->signature != 0x5452415020494645ULL) {
serial_write("[DISK] GPT signature missing on /dev/");
serial_write(disk->devname);
serial_write("\n");
kfree(buffer);
return;
}
uint32_t num_entries = hdr->num_partition_entries;
uint32_t entry_size = hdr->size_of_partition_entry;
uint64_t entry_lba = hdr->partition_entry_lba;
uint8_t *entry_buf = (uint8_t*)kmalloc(512);
if (!entry_buf) { kfree(buffer); return; }
int part_num = 1;
int part_count = 0;
for (uint32_t i = 0; i < num_entries && i < 128; i++) {
uint32_t entry_lba_offset = (uint32_t)entry_lba + (i * entry_size) / 512;
uint32_t entry_sector_offset = (i * entry_size) % 512;
if (disk->read_sector(disk, entry_lba_offset, entry_buf) != 0) break;
GPT_Entry *entry = (GPT_Entry *)(entry_buf + entry_sector_offset);
bool zero = true;
for (int j = 0; j < 16; j++) if (entry->type_guid[j] != 0) { zero = false; break; }
if (zero) continue;
uint32_t start = (uint32_t)entry->start_lba;
uint32_t end = (uint32_t)entry->end_lba;
uint32_t size = end - start + 1;
if (size == 0) continue;
static const uint8_t esp_guid[16] = {
0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11,
0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B
};
bool is_esp = true;
for (int j = 0; j < 16; j++) if (entry->type_guid[j] != esp_guid[j]) { is_esp = false; break; }
bool fat32 = false;
if (is_esp) fat32 = true;
else {
uint8_t *pbuf = (uint8_t*)kmalloc(512);
if (pbuf) {
if (disk->read_sector(disk, start, pbuf) == 0) {
fat32 = is_fat32_bpb(pbuf);
}
kfree(pbuf);
}
}
disk_register_partition(disk, start, size, fat32, is_esp, part_num++);
part_count++;
}
if (part_count == 0) {
serial_write("[DISK] GPT found but no partitions registered on /dev/");
serial_write(disk->devname);
serial_write("\n");
}
kfree(entry_buf);
kfree(buffer);
}
int disk_rescan(Disk *disk) {
if (!disk || disk->is_partition) return -1;
disk_remove_partitions(disk);
serial_write("[DISK] Rescanning /dev/");
serial_write(disk->devname);
serial_write("\n");
uint8_t *buffer = (uint8_t*)kmalloc(512);
if (buffer) {
if (disk->read_sector(disk, 1, buffer) == 0) {
GPT_Header *hdr = (GPT_Header*)buffer;
if (hdr->signature == 0x5452415020494645ULL) {
serial_write("[DISK] GPT detected on /dev/");
serial_write(disk->devname);
serial_write("\n");
kfree(buffer);
parse_gpt_partitions(disk);
return 0;
}
} else {
serial_write("[DISK] GPT probe read failed on /dev/");
serial_write(disk->devname);
serial_write("\n");
}
kfree(buffer);
}
parse_mbr_partitions(disk);
return 0;
}

View file

@ -109,6 +109,9 @@ uint8_t mouse_read(void) {
void mouse_init(void) { void mouse_init(void) {
uint8_t status; uint8_t status;
int limit = 128;
while (limit-- > 0 && (inb(PS2_STATUS_PORT) & PS2_STATUS_OUT_FULL))
(void)inb(PS2_DATA_PORT);
// Enable Aux Device // Enable Aux Device
mouse_wait(0); mouse_wait(0);

View file

@ -4,11 +4,19 @@
#ifndef PS2_H #ifndef PS2_H
#define PS2_H #define PS2_H
#define PS2_DATA_PORT 0x60
#define PS2_CMD_PORT 0x64
#define PS2_STATUS_PORT 0x64
#define PS2_STATUS_OUT_FULL 0x01
#define PS2_STATUS_IN_FULL 0x02
#define PS2_STATUS_AUX_DATA 0x20
#include <stdint.h> #include <stdint.h>
void ps2_init(void); void ps2_init(void);
#include "process.h" #include "process.h"
uint64_t timer_handler(registers_t *regs); uint64_t timer_handler(registers_t *regs);
uint64_t keyboard_handler(registers_t *regs); uint64_t keyboard_handler(registers_t *regs);
uint64_t mouse_handler(registers_t *regs); uint64_t mouse_handler(registers_t *regs);

233
src/drivers/ACPI/acpi.c Normal file
View file

@ -0,0 +1,233 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdint.h>
#include <stddef.h>
#include "acpi_structures.h"
#include "../I2C/acpi_i2c.h"
#include "acpi.h"
#include "../sys/idt.h"
#include "../core/limine.h"
#include "../core/panic.h"
#include "../core/platform.h"
#include "../core/kconsole.h"
#define MAX_ISO 16
static struct acpi_rsdp *acpi_rsdp = NULL;
static struct acpi_madt *acpi_madt = NULL;
static fadt_t *acpi_fadt = NULL; // my header file sucks ill make this nicer but its fine
static struct {
uint8_t source;
uint32_t gsi;
uint16_t flags;
} iso_table[MAX_ISO];
static uint8_t iso_count = 0;
/*
Each ACPI table contains an 8-bit checksum field.
When all bytes in the table are added together (including the checksum byte),
the lower 8 bits of the total sum must be zero for it to pass.
*/
static int acpi_checksum(void *ptr, size_t len) {
uint8_t sum = 0;
uint8_t *p = ptr;
for (size_t i = 0; i < len; i++)
sum += p[i];
return sum == 0;
}
struct acpi_rsdp *acpi_get_rsdp(void){
extern volatile struct limine_rsdp_request acpi_rsdp_request;
if (!acpi_rsdp_request.response)
kernel_panic(NULL, "ACPI RSDP not provided by the Bootloader");
if (!acpi_rsdp_request.response->address)
kernel_panic(NULL, "ACPI Invalid RSDP address provided by Bootloader");
return acpi_rsdp_request.response->address;
}
struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) {
struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address);
if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) {
size_t entries = (acpi_xsdt->header.length - sizeof(struct acpi_sdt)) / 8;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *tbl = (struct acpi_sdt *)p2v(acpi_xsdt->tables[i]);
if (!tbl) continue;
if (!memcmp(tbl->signature, signature, 4))
return tbl;
}
}
}
// RSDT fallback
if (!acpi_rsdp->rsdt_address)
return NULL;
struct acpi_sdt *acpi_rsdt = (struct acpi_sdt *)p2v(acpi_rsdp->rsdt_address);
if (!acpi_checksum(acpi_rsdt, acpi_rsdt->length))
return NULL;
uint32_t *tables = (uint32_t *)((uint8_t *)acpi_rsdt + sizeof(struct acpi_sdt));
size_t entries = (acpi_rsdt->length - sizeof(struct acpi_sdt)) / 4;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *tbl = (struct acpi_sdt *)p2v(tables[i]);
if (!tbl) continue;
if (!memcmp(tbl->signature, signature, 4))
return tbl;
}
return NULL;
}
uint16_t SLP_TYPa = 0;
uint16_t SLP_TYPb = 0;
void acpi_parse_s5(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return;
char *dsdt = (char *)p2v((uintptr_t)acpi_fadt->dsdt);
if (memcmp(dsdt, "DSDT", 4) != 0) return;
uint32_t dsdt_len = *(uint32_t*)(dsdt + 4);
char *ptr = dsdt + 36;
char *end = dsdt + dsdt_len;
while (ptr < end) {
if (memcmp(ptr, "_S5_", 4) == 0) {
ptr += 4;
if (*ptr == 0x12) {
ptr += 3;
if (*ptr == 0x0A) ptr++;
SLP_TYPa = (*(uint8_t*)ptr) << 10;
ptr++;
if (*ptr == 0x0A) ptr++;
SLP_TYPb = (*(uint8_t*)ptr) << 10;
return;
}
}
ptr++;
}
}
__attribute__((noreturn))
void acpi_shutdown(void) {
if (SLP_TYPa == 0) acpi_parse_s5();
if (SLP_TYPa == 0) SLP_TYPa = (5 << 10);
outw((uint16_t)acpi_fadt->pm1a_cnt_blk, SLP_TYPa | SLP_EN);
if (acpi_fadt->pm1b_cnt_blk != 0) {
outw((uint16_t)acpi_fadt->pm1b_cnt_blk, SLP_TYPb | SLP_EN);
}
//virtulizers last just incase these have some sort of unintended effect on real hw
outw(0xB004, 0x2000); // bochs
outw(0x4004, 0x3400); // vbox
outw(0x604, 0x2000); // QEMU
outw(0x600, 0x34); // Cloud Hypervisor
asm volatile("cli");
for(;;) asm volatile("hlt");
}
int acpi_init(void){
acpi_rsdp = acpi_get_rsdp();
if (!acpi_rsdp)
kernel_panic(NULL, "ACPI does not provide a required RSDP");
size_t rsdp_len = acpi_rsdp->revision >= 2 ? acpi_rsdp->length : 20;
if (!acpi_checksum(acpi_rsdp, rsdp_len))
kernel_panic(NULL, "bad RSDP checksum");
acpi_fadt = (struct acpi_fadt *)acpi_get_sdt("FACP");
if (!acpi_fadt)
kernel_panic(NULL, "FADT not found");
if (!acpi_checksum(acpi_fadt, acpi_fadt->header.length))
kernel_panic(NULL, "bad FADT checksum");
if (acpi_fadt->smi_cmd && acpi_fadt->acpi_enable) {
outb(acpi_fadt->smi_cmd, acpi_fadt->acpi_enable);
int timeout = 1000000;
while (!(inw(acpi_fadt->pm1a_cnt_blk) & 1) && timeout-- > 0)
asm("pause");
if (timeout <= 0) {
kernel_panic(NULL, "Enable timeout");
}
}
acpi_madt = (struct acpi_madt *)acpi_get_sdt("APIC");
if (!acpi_madt)
kernel_panic(NULL, "MADT not found");
if (!acpi_checksum(acpi_madt, acpi_madt->header.length))
kernel_panic(NULL, "bad MADT checksum");
uint8_t *ptr = acpi_madt->entries;
uint8_t *end = (uint8_t *)acpi_madt + acpi_madt->header.length;
while (ptr < end) {
struct madt_entry_header *h = (void *)ptr;
if (h->length < sizeof(struct madt_entry_header))
break;
if (ptr + h->length > end)
break;
switch (h->type) {
case 2: {
if (iso_count < MAX_ISO) {
struct madt_iso *iso = (void *)ptr;
iso_table[iso_count].source = iso->source;
iso_table[iso_count].gsi = iso->gsi;
iso_table[iso_count].flags = iso->flags;
iso_count++;
}
break;
}
default:
break;
}
ptr += h->length;
}
acpi_i2c_enumerate();
return 0;
}
uint32_t acpi_irq_to_gsi(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq)
return iso_table[i].gsi;
}
return irq;
}
uint16_t acpi_irq_flags(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq)
return iso_table[i].flags;
}
return 0;
}
struct acpi_sdt *acpi_get_dsdt(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return NULL;
return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt);
}

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

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

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

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

View file

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

View file

@ -0,0 +1,133 @@
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef ACPI_STRUCTURES_H
#define ACPI_STRUCTURES_H
#include <stdint.h>
struct acpi_sdt {
char signature[4];
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oem_id[6];
char oem_table_id[8];
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
} __attribute__((packed));
struct acpi_rsdp {
char signature[8];
uint8_t checksum;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
// acpi2
uint32_t length;
uint64_t xsdt_address;
uint8_t extended_checksum;
uint8_t reserved[3];
} __attribute__((packed));
struct acpi_xsdt {
struct acpi_sdt header;
uint64_t tables[];
} __attribute__((packed));
typedef struct
{
uint8_t address_space_id;
uint8_t register_bit_width;
uint8_t register_bit_offset;
uint8_t access_size;
uint64_t address;
} __attribute__((packed)) GenericAddressStructure;
typedef struct acpi_fadt
{
struct acpi_sdt header;
uint32_t firmware_ctrl;
uint32_t dsdt;
uint8_t __reserved;
uint8_t preferred_pm_profile;
uint16_t sci_int;
uint32_t smi_cmd;
uint8_t acpi_enable;
uint8_t acpi_disable;
uint8_t s4bios_req;
uint8_t pstate_cnt;
uint32_t pm1a_evt_blk;
uint32_t pm1b_evt_blk;
uint32_t pm1a_cnt_blk;
uint32_t pm1b_cnt_blk;
uint32_t pm2_cnt_blk;
uint32_t pm_tmr_blk;
uint32_t gpe0_blk;
uint32_t gpe1_blk;
uint8_t pm1_evt_len;
uint8_t pm1_cnt_len;
uint8_t pm2_cnt_len;
uint8_t pm_tmr_len;
uint8_t gpe0_blk_len;
uint8_t gpe1_blk_len;
uint8_t gpe1_base;
uint8_t cst_cnt;
uint16_t p_lvl2_lat;
uint16_t p_lvl3_lat;
uint16_t flush_size;
uint16_t flush_stride;
uint8_t duty_offset;
uint8_t duty_width;
uint8_t day_alrm;
uint8_t mon_alrm;
uint8_t century;
uint16_t iapc_boot_arch;
uint8_t __reserved2;
uint32_t flags;
GenericAddressStructure reset_reg;
uint8_t reset_value;
uint16_t arm_boot_arch;
uint8_t fadt_minor_version;
uint64_t x_firmware_ctrl;
uint64_t x_dsdt;
uint8_t x_pm1a_evt_blk[12];
uint8_t x_pm1b_evt_blk[12];
uint8_t x_pm1a_cnt_blk[12];
uint8_t x_pm1b_cnt_blk[12];
uint8_t x_pm2_cnt_blk[12];
uint8_t x_pm_tmr_blk[12];
uint8_t x_gpe0_blk[12];
uint8_t x_gpe1_blk[12];
uint8_t sleep_control_reg[12];
uint8_t sleep_status_reg[12];
uint64_t hypervison_vendor_identity;
} __attribute__((packed)) fadt_t;
struct acpi_madt {
struct acpi_sdt header;
uint32_t lapic_addr;
uint32_t flags;
uint8_t entries[];
} __attribute__((packed));
struct madt_iso {
uint8_t type;
uint8_t length;
uint8_t bus;
uint8_t source;
uint32_t gsi;
uint16_t flags;
} __attribute__((packed));
struct madt_entry_header {
uint8_t type;
uint8_t length;
} __attribute__((packed));
struct acpi_sdt *acpi_find_sdt(const char sig[4]);
#endif

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

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

View file

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

View file

@ -3,6 +3,8 @@
// This header needs to maintain in any file it is present in, as per the GPL license terms. // This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "bootfs.h" #include "bootfs.h"
#include "disk.h"
#include "fat32.h"
#include "../sys/bootfs_state.h" #include "../sys/bootfs_state.h"
#include "vfs.h" #include "vfs.h"
#include "core/kutils.h" #include "core/kutils.h"
@ -13,6 +15,14 @@
extern void serial_write(const char *str); extern void serial_write(const char *str);
extern void serial_write_hex(uint64_t value); extern void serial_write_hex(uint64_t value);
typedef struct bootfs_custom_file {
char name[128]; /* filename relative to /boot, e.g. "boredos.elf" */
uint8_t *data;
uint32_t size;
uint32_t capacity; /* 0 = read-only initrd pointer; >0 = heap-allocated writable */
struct bootfs_custom_file *next;
} bootfs_custom_file_t;
typedef struct { typedef struct {
char path[512]; char path[512];
int offset; int offset;
@ -55,16 +65,42 @@ static vfs_fs_ops_t bootfs_ops = {
}; };
bootfs_state_t g_bootfs_state = {0}; bootfs_state_t g_bootfs_state = {0};
static char g_limine_conf_path[64] = "";
static bootfs_custom_file_t *bootfs_find_custom(const char *name) {
bootfs_custom_file_t *f = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
while (f) {
if (strcmp(f->name, name) == 0) return f;
f = f->next;
}
return NULL;
}
void bootfs_register_file(const char *name, void *data, uint32_t size) {
if (!name || !data) return;
bootfs_custom_file_t *f = bootfs_find_custom(name);
if (!f) {
f = (bootfs_custom_file_t*)kmalloc(sizeof(bootfs_custom_file_t));
if (!f) return;
memset(f, 0, sizeof(bootfs_custom_file_t));
strcpy(f->name, name);
f->next = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
g_bootfs_state.custom_files = f;
}
f->data = (uint8_t*)data;
f->size = size;
f->capacity = 0;
}
static bool is_metadata_path(const char *path) { static bool is_metadata_path(const char *path) {
if (!path) return false; if (!path) return false;
return k_strncmp(path, "metadata", 8) == 0; return strncmp(path, "metadata", 8) == 0;
} }
static bool is_metadata_file(const char *path) { static bool is_metadata_file(const char *path) {
if (k_strcmp(path, "metadata/boot_time") == 0) return true; if (strcmp(path, "metadata/boot_time") == 0) return true;
if (k_strcmp(path, "metadata/boot_flags") == 0) return true; if (strcmp(path, "metadata/boot_flags") == 0) return true;
if (k_strcmp(path, "metadata/version") == 0) return true; if (strcmp(path, "metadata/version") == 0) return true;
return false; return false;
} }
@ -75,8 +111,8 @@ static void* bootfs_open(void *fs_private, const char *path, const char *mode) {
bootfs_handle_t *h = (bootfs_handle_t*)kmalloc(sizeof(bootfs_handle_t)); bootfs_handle_t *h = (bootfs_handle_t*)kmalloc(sizeof(bootfs_handle_t));
if (!h) return NULL; if (!h) return NULL;
k_memset(h, 0, sizeof(bootfs_handle_t)); memset(h, 0, sizeof(bootfs_handle_t));
k_strcpy(h->path, path); strcpy(h->path, path);
h->offset = 0; h->offset = 0;
if (path[0] == '\0') { if (path[0] == '\0') {
@ -98,28 +134,28 @@ static int generate_metadata_content(const char *file, char *buffer, int max_siz
buffer[0] = '\0'; buffer[0] = '\0';
int len = 0; int len = 0;
if (k_strcmp(file, "metadata/boot_time") == 0) { if (strcmp(file, "metadata/boot_time") == 0) {
extern uint32_t wm_get_ticks(void); extern uint32_t wm_get_ticks(void);
uint32_t ticks = wm_get_ticks(); uint32_t ticks = wm_get_ticks();
k_strcpy(buffer, "Boot time: "); strcpy(buffer, "Boot time: ");
char time_buf[32]; char time_buf[32];
k_itoa(g_bootfs_state.boot_time_ms, time_buf); itoa(g_bootfs_state.boot_time_ms, time_buf);
k_strcpy(buffer + k_strlen(buffer), time_buf); strcpy(buffer + strlen(buffer), time_buf);
k_strcpy(buffer + k_strlen(buffer), " ms\nTicks: "); strcpy(buffer + strlen(buffer), " ms\nTicks: ");
k_itoa(ticks, time_buf); itoa(ticks, time_buf);
k_strcpy(buffer + k_strlen(buffer), time_buf); strcpy(buffer + strlen(buffer), time_buf);
k_strcpy(buffer + k_strlen(buffer), "\n"); strcpy(buffer + strlen(buffer), "\n");
len = k_strlen(buffer); len = strlen(buffer);
} else if (k_strcmp(file, "metadata/version") == 0) { } else if (strcmp(file, "metadata/version") == 0) {
k_strcpy(buffer, "Bootloader: "); strcpy(buffer, "Bootloader: ");
k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_name); strcpy(buffer + strlen(buffer), g_bootfs_state.bootloader_name);
k_strcpy(buffer + k_strlen(buffer), "\nVersion: "); strcpy(buffer + strlen(buffer), "\nVersion: ");
k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_version); strcpy(buffer + strlen(buffer), g_bootfs_state.bootloader_version);
k_strcpy(buffer + k_strlen(buffer), "\n"); strcpy(buffer + strlen(buffer), "\n");
len = k_strlen(buffer); len = strlen(buffer);
} else if (k_strcmp(file, "metadata/boot_flags") == 0) { } else if (strcmp(file, "metadata/boot_flags") == 0) {
k_strcpy(buffer, "Boot flags: 0x"); strcpy(buffer, "Boot flags: 0x");
char flags_buf[8]; char flags_buf[8];
uint8_t flags = g_bootfs_state.boot_flags; uint8_t flags = g_bootfs_state.boot_flags;
int hex_digit = (flags >> 4) & 0xF; int hex_digit = (flags >> 4) & 0xF;
@ -128,8 +164,8 @@ static int generate_metadata_content(const char *file, char *buffer, int max_siz
flags_buf[1] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10); flags_buf[1] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10);
flags_buf[2] = '\n'; flags_buf[2] = '\n';
flags_buf[3] = '\0'; flags_buf[3] = '\0';
k_strcpy(buffer + k_strlen(buffer), flags_buf); strcpy(buffer + strlen(buffer), flags_buf);
len = k_strlen(buffer); len = strlen(buffer);
} }
return len; return len;
@ -144,27 +180,45 @@ static int bootfs_read(void *fs_private, void *handle, void *buf, int size) {
int content_len = 0; int content_len = 0;
if (k_strcmp(h->path, "limine.conf") == 0) { if (strcmp(h->path, "limine.conf") == 0) {
k_memcpy(content_buffer, g_bootfs_state.limine_conf, memcpy(content_buffer, g_bootfs_state.limine_conf,
g_bootfs_state.limine_conf_len); g_bootfs_state.limine_conf_len);
content_len = g_bootfs_state.limine_conf_len; content_len = g_bootfs_state.limine_conf_len;
} else if (k_strcmp(h->path, "kernel") == 0) { } else if (strcmp(h->path, "kernel") == 0) {
k_strcpy(content_buffer, "Kernel reference\nSize: "); strcpy(content_buffer, "Kernel reference\nSize: ");
char size_buf[32]; char size_buf[32];
k_itoa(g_bootfs_state.kernel_size, size_buf); itoa(g_bootfs_state.kernel_size, size_buf);
k_strcpy(content_buffer + k_strlen(content_buffer), size_buf); strcpy(content_buffer + strlen(content_buffer), size_buf);
k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n"); strcpy(content_buffer + strlen(content_buffer), " bytes\n");
content_len = k_strlen(content_buffer); content_len = strlen(content_buffer);
} else if (k_strcmp(h->path, "initrd") == 0) { } else if (strcmp(h->path, "initrd") == 0) {
k_strcpy(content_buffer, "Initial ramdisk reference\nSize: "); strcpy(content_buffer, "Initial ramdisk reference\nSize: ");
char size_buf[32]; char size_buf[32];
k_itoa(g_bootfs_state.initrd_size, size_buf); itoa(g_bootfs_state.initrd_size, size_buf);
k_strcpy(content_buffer + k_strlen(content_buffer), size_buf); strcpy(content_buffer + strlen(content_buffer), size_buf);
k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n"); strcpy(content_buffer + strlen(content_buffer), " bytes\n");
content_len = k_strlen(content_buffer); content_len = strlen(content_buffer);
} else if (strcmp(h->path, "initrd.tar") == 0) {
kfree(content_buffer);
if (h->offset >= (int)g_bootfs_state.initrd_size) return 0;
int avail = (int)g_bootfs_state.initrd_size - h->offset;
int to_read = (size < avail) ? size : avail;
memcpy(buf, (uint8_t*)g_bootfs_state.initrd_ptr + h->offset, to_read);
h->offset += to_read;
return to_read;
} else if (is_metadata_file(h->path)) { } else if (is_metadata_file(h->path)) {
content_len = generate_metadata_content(h->path, content_buffer, 4096); content_len = generate_metadata_content(h->path, content_buffer, 4096);
} else { } else {
bootfs_custom_file_t *cf = bootfs_find_custom(h->path);
if (cf) {
kfree(content_buffer);
if (h->offset >= (int)cf->size) return 0;
int avail = (int)cf->size - h->offset;
int to_read = (avail < size) ? avail : size;
memcpy(buf, cf->data + h->offset, to_read);
h->offset += to_read;
return to_read;
}
kfree(content_buffer); kfree(content_buffer);
return -1; return -1;
} }
@ -178,7 +232,7 @@ static int bootfs_read(void *fs_private, void *handle, void *buf, int size) {
int available = content_len - h->offset; int available = content_len - h->offset;
int read_size = (available < size) ? available : size; int read_size = (available < size) ? available : size;
k_memcpy(buf, content_buffer + h->offset, read_size); memcpy(buf, content_buffer + h->offset, read_size);
h->offset += read_size; h->offset += read_size;
kfree(content_buffer); kfree(content_buffer);
@ -189,7 +243,7 @@ static int bootfs_write(void *fs_private, void *handle, const void *buf, int siz
bootfs_handle_t *h = (bootfs_handle_t*)handle; bootfs_handle_t *h = (bootfs_handle_t*)handle;
if (!h || !buf || size <= 0) return -1; if (!h || !buf || size <= 0) return -1;
if (k_strcmp(h->path, "limine.conf") != 0) { if (strcmp(h->path, "limine.conf") != 0) {
return -1; return -1;
} }
@ -197,22 +251,20 @@ static int bootfs_write(void *fs_private, void *handle, const void *buf, int siz
if (max_write <= 0) return -1; if (max_write <= 0) return -1;
int write_size = (size < max_write) ? size : max_write; int write_size = (size < max_write) ? size : max_write;
k_memcpy(g_bootfs_state.limine_conf + h->offset, buf, write_size); memcpy(g_bootfs_state.limine_conf + h->offset, buf, write_size);
h->offset += write_size; h->offset += write_size;
if (h->offset > g_bootfs_state.limine_conf_len) { if (h->offset > g_bootfs_state.limine_conf_len) {
g_bootfs_state.limine_conf_len = h->offset; g_bootfs_state.limine_conf_len = h->offset;
} }
extern vfs_file_t* vfs_open(const char *path, const char *mode); if (g_limine_conf_path[0] != '\0') {
extern int vfs_write(vfs_file_t *file, const void *buf, int size); vfs_file_t *fat_conf = vfs_open(g_limine_conf_path, "w");
extern void vfs_close(vfs_file_t *file);
vfs_file_t *fat_conf = vfs_open("/limine.conf", "w");
if (fat_conf) { if (fat_conf) {
vfs_write(fat_conf, g_bootfs_state.limine_conf, g_bootfs_state.limine_conf_len); vfs_write(fat_conf, g_bootfs_state.limine_conf, g_bootfs_state.limine_conf_len);
vfs_close(fat_conf); vfs_close(fat_conf);
} }
}
return write_size; return write_size;
} }
@ -247,34 +299,49 @@ static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *
if (rel_path[0] == '\0') { if (rel_path[0] == '\0') {
if (count < max) { if (count < max) {
k_strcpy(entries[count].name, "limine.conf"); strcpy(entries[count].name, "limine.conf");
entries[count].size = g_bootfs_state.limine_conf_len; entries[count].size = g_bootfs_state.limine_conf_len;
entries[count].is_directory = 0; entries[count].is_directory = 0;
count++; count++;
} }
if (count < max) { if (count < max) {
k_strcpy(entries[count].name, "kernel"); strcpy(entries[count].name, "kernel");
entries[count].size = g_bootfs_state.kernel_size; entries[count].size = g_bootfs_state.kernel_size;
entries[count].is_directory = 0; entries[count].is_directory = 0;
count++; count++;
} }
if (count < max) { if (count < max) {
k_strcpy(entries[count].name, "initrd"); strcpy(entries[count].name, "initrd");
entries[count].size = g_bootfs_state.initrd_size; entries[count].size = g_bootfs_state.initrd_size;
entries[count].is_directory = 0; entries[count].is_directory = 0;
count++; count++;
} }
if (count < max) { if (count < max) {
k_strcpy(entries[count].name, "metadata"); strcpy(entries[count].name, "initrd.tar");
entries[count].size = g_bootfs_state.initrd_size;
entries[count].is_directory = 0;
count++;
}
if (count < max) {
strcpy(entries[count].name, "metadata");
entries[count].size = 0; entries[count].size = 0;
entries[count].is_directory = 1; entries[count].is_directory = 1;
count++; count++;
} }
bootfs_custom_file_t *cf = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
while (cf && count < max) {
strcpy(entries[count].name, cf->name);
entries[count].size = cf->size;
entries[count].is_directory = 0;
count++;
cf = cf->next;
} }
else if (k_strcmp(rel_path, "metadata") == 0) { }
else if (strcmp(rel_path, "metadata") == 0) {
const char *meta_files[] = { const char *meta_files[] = {
"boot_time", "boot_time",
"boot_flags", "boot_flags",
@ -282,7 +349,7 @@ static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *
}; };
for (int i = 0; i < 3 && count < max; i++) { for (int i = 0; i < 3 && count < max; i++) {
k_strcpy(entries[count].name, meta_files[i]); strcpy(entries[count].name, meta_files[i]);
entries[count].size = 0; entries[count].size = 0;
entries[count].is_directory = 0; entries[count].is_directory = 0;
count++; count++;
@ -300,7 +367,7 @@ static bool bootfs_rmdir(void *fs_private, const char *rel_path) {
if (!rel_path) rel_path = ""; if (!rel_path) rel_path = "";
if (rel_path[0] == '/') rel_path++; if (rel_path[0] == '/') rel_path++;
if (k_strcmp(rel_path, "metadata") == 0) { if (strcmp(rel_path, "metadata") == 0) {
return false; /* metadata directory is protected */ return false; /* metadata directory is protected */
} }
@ -312,7 +379,7 @@ static bool bootfs_unlink(void *fs_private, const char *rel_path) {
if (rel_path[0] == '/') rel_path++; if (rel_path[0] == '/') rel_path++;
/* Only limine.conf can be deleted */ /* Only limine.conf can be deleted */
if (k_strcmp(rel_path, "limine.conf") != 0) { if (strcmp(rel_path, "limine.conf") != 0) {
return false; return false;
} }
@ -344,24 +411,24 @@ static bool bootfs_rename(void *fs_private, const char *old_path, const char *ne
if (new_rel[0] == '/') new_rel++; if (new_rel[0] == '/') new_rel++;
/* Only limine.conf can be renamed */ /* Only limine.conf can be renamed */
if (k_strcmp(old_rel, "limine.conf") != 0) { if (strcmp(old_rel, "limine.conf") != 0) {
return false; return false;
} }
/* kernel and initrd are protected */ /* kernel and initrd are protected */
if (k_strcmp(new_rel, "kernel") == 0 || k_strcmp(new_rel, "initrd") == 0) { if (strcmp(new_rel, "kernel") == 0 || strcmp(new_rel, "initrd") == 0) {
return false; return false;
} }
/* metadata directory is protected */ /* metadata directory is protected */
if (k_strncmp(new_rel, "metadata", 8) == 0) { if (strncmp(new_rel, "metadata", 8) == 0) {
return false; return false;
} }
extern bool vfs_rename(const char *old_path, const char *new_path); extern bool vfs_rename(const char *old_path, const char *new_path);
char new_partition_path[256]; char new_partition_path[256];
k_strcpy(new_partition_path, "/"); strcpy(new_partition_path, "/");
/* Manually append new_rel to new_partition_path */ /* Manually append new_rel to new_partition_path */
int path_len = 0; int path_len = 0;
@ -375,7 +442,7 @@ static bool bootfs_rename(void *fs_private, const char *old_path, const char *ne
return false; return false;
} }
k_memcpy(new_partition_path + path_len, new_rel, rel_len + 1); memcpy(new_partition_path + path_len, new_rel, rel_len + 1);
/* Rename on partition filesystem */ /* Rename on partition filesystem */
bool result = vfs_rename("/limine.conf", new_partition_path); bool result = vfs_rename("/limine.conf", new_partition_path);
@ -399,12 +466,15 @@ static bool bootfs_exists(void *fs_private, const char *rel_path) {
if (rel_path[0] == '\0') return true; if (rel_path[0] == '\0') return true;
if (k_strcmp(rel_path, "limine.conf") == 0) return true; if (strcmp(rel_path, "limine.conf") == 0) return true;
if (k_strcmp(rel_path, "kernel") == 0) return true; if (strcmp(rel_path, "efi") == 0) return true;
if (k_strcmp(rel_path, "initrd") == 0) return true; if (strcmp(rel_path, "kernel") == 0) return true;
if (strcmp(rel_path, "initrd") == 0) return true;
if (strcmp(rel_path, "initrd.tar") == 0) return true;
if (k_strcmp(rel_path, "metadata") == 0) return true; if (strcmp(rel_path, "metadata") == 0) return true;
if (is_metadata_file(rel_path)) return true; if (is_metadata_file(rel_path)) return true;
if (bootfs_find_custom(rel_path)) return true;
return false; return false;
} }
@ -414,7 +484,8 @@ static bool bootfs_is_dir(void *fs_private, const char *rel_path) {
if (rel_path[0] == '/') rel_path++; if (rel_path[0] == '/') rel_path++;
if (rel_path[0] == '\0') return true; if (rel_path[0] == '\0') return true;
if (k_strcmp(rel_path, "metadata") == 0) return true; if (strcmp(rel_path, "efi") == 0) return true;
if (strcmp(rel_path, "metadata") == 0) return true;
return false; return false;
} }
@ -424,37 +495,42 @@ static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t
if (!rel_path) rel_path = ""; if (!rel_path) rel_path = "";
if (rel_path[0] == '/') rel_path++; if (rel_path[0] == '/') rel_path++;
k_memset(info, 0, sizeof(vfs_dirent_t)); memset(info, 0, sizeof(vfs_dirent_t));
if (rel_path[0] == '\0') { if (rel_path[0] == '\0') {
k_strcpy(info->name, "/"); strcpy(info->name, "/");
info->is_directory = 1; info->is_directory = 1;
return 0; return 0;
} }
if (k_strcmp(rel_path, "limine.conf") == 0) { if (strcmp(rel_path, "limine.conf") == 0) {
k_strcpy(info->name, "limine.conf"); strcpy(info->name, "limine.conf");
info->size = g_bootfs_state.limine_conf_len; info->size = g_bootfs_state.limine_conf_len;
info->is_directory = 0; info->is_directory = 0;
return 0; return 0;
} }
if (k_strcmp(rel_path, "kernel") == 0) { if (strcmp(rel_path, "kernel") == 0) {
k_strcpy(info->name, "kernel"); strcpy(info->name, "kernel");
info->size = g_bootfs_state.kernel_size; info->size = g_bootfs_state.kernel_size;
info->is_directory = 0; info->is_directory = 0;
return 0; return 0;
} } else if (strcmp(rel_path, "initrd") == 0) {
strcpy(info->name, "initrd");
if (k_strcmp(rel_path, "initrd") == 0) {
k_strcpy(info->name, "initrd");
info->size = g_bootfs_state.initrd_size; info->size = g_bootfs_state.initrd_size;
info->is_directory = 0; info->is_directory = 0;
return 0; return 0;
} } else if (strcmp(rel_path, "initrd.tar") == 0) {
strcpy(info->name, "initrd.tar");
if (k_strcmp(rel_path, "metadata") == 0) { info->size = g_bootfs_state.initrd_size;
k_strcpy(info->name, "metadata"); info->is_directory = 0;
return 0;
} else if (strcmp(rel_path, "metadata") == 0) {
strcpy(info->name, "metadata");
info->is_directory = 1;
return 0;
} else if (strcmp(rel_path, "efi") == 0) {
strcpy(info->name, "efi");
info->is_directory = 1; info->is_directory = 1;
return 0; return 0;
} }
@ -462,12 +538,20 @@ static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t
if (is_metadata_file(rel_path)) { if (is_metadata_file(rel_path)) {
char temp_buf[4096]; char temp_buf[4096];
int len = generate_metadata_content(rel_path, temp_buf, 4096); int len = generate_metadata_content(rel_path, temp_buf, 4096);
k_strcpy(info->name, rel_path + 9); strcpy(info->name, rel_path + 9);
info->size = len; info->size = len;
info->is_directory = 0; info->is_directory = 0;
return 0; return 0;
} }
bootfs_custom_file_t *cf = bootfs_find_custom(rel_path);
if (cf) {
strcpy(info->name, cf->name);
info->size = cf->size;
info->is_directory = 0;
return 0;
}
return -1; return -1;
} }
@ -481,17 +565,22 @@ static uint32_t bootfs_get_size(void *file_handle) {
bootfs_handle_t *h = (bootfs_handle_t*)file_handle; bootfs_handle_t *h = (bootfs_handle_t*)file_handle;
if (!h) return 0; if (!h) return 0;
if (k_strcmp(h->path, "limine.conf") == 0) { if (strcmp(h->path, "limine.conf") == 0) {
return g_bootfs_state.limine_conf_len; return g_bootfs_state.limine_conf_len;
} else if (k_strcmp(h->path, "kernel") == 0) { } else if (strcmp(h->path, "kernel") == 0) {
return g_bootfs_state.kernel_size; return g_bootfs_state.kernel_size;
} else if (k_strcmp(h->path, "initrd") == 0) { } else if (strcmp(h->path, "initrd") == 0) {
return g_bootfs_state.initrd_size;
} else if (strcmp(h->path, "initrd.tar") == 0) {
return g_bootfs_state.initrd_size; return g_bootfs_state.initrd_size;
} else if (is_metadata_file(h->path)) { } else if (is_metadata_file(h->path)) {
char temp_buf[4096]; char temp_buf[4096];
return generate_metadata_content(h->path, temp_buf, 4096); return generate_metadata_content(h->path, temp_buf, 4096);
} }
bootfs_custom_file_t *cf = bootfs_find_custom(h->path);
if (cf) return cf->size;
return 0; return 0;
} }
@ -500,10 +589,10 @@ vfs_fs_ops_t* bootfs_get_ops(void) {
} }
void bootfs_state_init(void) { void bootfs_state_init(void) {
k_memset(&g_bootfs_state, 0, sizeof(bootfs_state_t)); memset(&g_bootfs_state, 0, sizeof(bootfs_state_t));
k_strcpy(g_bootfs_state.bootloader_name, "Limine"); strcpy(g_bootfs_state.bootloader_name, "Limine");
k_strcpy(g_bootfs_state.bootloader_version, "6.0.0"); strcpy(g_bootfs_state.bootloader_version, "6.0.0");
g_bootfs_state.limine_conf[0] = '\0'; g_bootfs_state.limine_conf[0] = '\0';
@ -518,24 +607,55 @@ void bootfs_init(void) {
bootfs_state_init(); bootfs_state_init();
} }
void bootfs_mount_boot_partition(void) {
int count = disk_get_count();
Disk *esp = NULL;
for (int i = 0; i < count; i++) {
Disk *d = disk_get_by_index(i);
if (d && d->is_esp) {
esp = d;
break;
}
}
if (esp) {
void *fs_private = fat32_mount_volume(esp);
if (fs_private) {
vfs_mount("/boot/efi", esp->devname, "fat32", fat32_get_realfs_ops(), fs_private);
serial_write("[BOOTFS] Mounted ESP at /boot/efi\n");
}
} else {
serial_write("[BOOTFS] No ESP found for mounting\n");
}
}
void bootfs_refresh_from_disk(void) { void bootfs_refresh_from_disk(void) {
extern vfs_file_t* vfs_open(const char *path, const char *mode); 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 int vfs_read(vfs_file_t *file, void *buf, int size);
extern void vfs_close(vfs_file_t *file); extern void vfs_close(vfs_file_t *file);
vfs_file_t *boot_conf = vfs_open("/limine.conf", "r"); vfs_file_t *boot_conf = vfs_open("/boot/efi/limine.conf", "r");
if (boot_conf) {
strcpy(g_limine_conf_path, "/boot/efi/limine.conf");
} else {
boot_conf = vfs_open("/limine.conf", "r");
if (boot_conf) {
strcpy(g_limine_conf_path, "/limine.conf");
}
}
if (boot_conf) { if (boot_conf) {
int bytes_read = vfs_read(boot_conf, g_bootfs_state.limine_conf, 2047); int bytes_read = vfs_read(boot_conf, g_bootfs_state.limine_conf, 2047);
if (bytes_read > 0) { if (bytes_read > 0) {
g_bootfs_state.limine_conf[bytes_read] = '\0'; g_bootfs_state.limine_conf[bytes_read] = '\0';
g_bootfs_state.limine_conf_len = bytes_read; g_bootfs_state.limine_conf_len = bytes_read;
serial_write("[BOOTFS] Loaded limine.conf from partition: "); serial_write("[BOOTFS] Loaded limine.conf from ");
extern void serial_write_hex(uint64_t value); serial_write(g_limine_conf_path);
serial_write_hex(bytes_read); serial_write("\n");
serial_write(" bytes\n");
} }
vfs_close(boot_conf); vfs_close(boot_conf);
} else { } else {
serial_write("[BOOTFS] Warning: /limine.conf not found on partition\n"); serial_write("[BOOTFS] Warning: limine.conf not found on disk\n");
} }
} }

View file

@ -7,7 +7,9 @@
#include "vfs.h" #include "vfs.h"
void bootfs_init(void); void bootfs_init(void);
void bootfs_mount_boot_partition(void);
void bootfs_refresh_from_disk(void); void bootfs_refresh_from_disk(void);
vfs_fs_ops_t* bootfs_get_ops(void); vfs_fs_ops_t* bootfs_get_ops(void);
void bootfs_register_file(const char *name, void *data, uint32_t size);
#endif #endif

View file

@ -15,7 +15,7 @@
static spinlock_t ramfs_lock = SPINLOCK_INIT; // Protects the RAM-based filesystem (/) static spinlock_t ramfs_lock = SPINLOCK_INIT; // Protects the RAM-based filesystem (/)
#define MAX_CLUSTERS 8192 #define MAX_CLUSTERS 32768
#define MAX_OPEN_HANDLES 32 #define MAX_OPEN_HANDLES 32
// In-memory FAT table // In-memory FAT table
@ -49,6 +49,7 @@ typedef struct {
uint32_t sectors_per_cluster; uint32_t sectors_per_cluster;
uint32_t root_cluster; uint32_t root_cluster;
uint32_t fat_size; // sectors uint32_t fat_size; // sectors
uint32_t num_fats;
uint32_t total_sectors; uint32_t total_sectors;
uint32_t partition_offset; // LBA offset of partition start uint32_t partition_offset; // LBA offset of partition start
bool mounted; bool mounted;
@ -60,11 +61,16 @@ typedef struct {
// Dynamically allocated volumes (no longer A-Z indexed) // Dynamically allocated volumes (no longer A-Z indexed)
static bool realfs_mkdir_vol(FAT32_Volume *vol, const char *path); static bool realfs_mkdir_vol(FAT32_Volume *vol, const char *path);
extern void serial_write(const char *str);
extern void serial_write_hex(uint32_t val);
#define MAX_REAL_VOLUMES 8 #define MAX_REAL_VOLUMES 8
static FAT32_Volume *real_volumes[MAX_REAL_VOLUMES]; static FAT32_Volume *real_volumes[MAX_REAL_VOLUMES];
static int real_volume_count = 0; static int real_volume_count = 0;
static FAT32_Volume *root_volume = NULL;
// Forward declarations for volume-aware functions // Forward declarations for volume-aware functions
static uint32_t realfs_allocate_cluster(FAT32_Volume *vol);
static void handle_fat32_truncate(FAT32_FileHandle *handle);
static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *path, const char *mode); static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *path, const char *mode);
static int realfs_list_directory_vol(FAT32_Volume *vol, const char *path, FAT32_FileInfo *entries, int max_entries); static int realfs_list_directory_vol(FAT32_Volume *vol, const char *path, FAT32_FileInfo *entries, int max_entries);
static bool realfs_delete_from_vol(FAT32_Volume *vol, const char *path); static bool realfs_delete_from_vol(FAT32_Volume *vol, const char *path);
@ -78,6 +84,16 @@ static bool realfs_find_contiguous_free(FAT32_Volume *vol, uint32_t dir_start_cl
static uint8_t fat_lfn_checksum(const uint8_t *short_name); static uint8_t fat_lfn_checksum(const uint8_t *short_name);
static void extract_lfn_chars(FAT32_LFNEntry *lfn, char *buffer); static void extract_lfn_chars(FAT32_LFNEntry *lfn, char *buffer);
static void to_dos_filename(const char *filename, char *dos_name); static void to_dos_filename(const char *filename, char *dos_name);
static bool realfs_create_entry(FAT32_Volume *vol, uint32_t parent_cluster, const char *name, uint8_t attributes, uint32_t start_cluster, uint32_t file_size, uint32_t *out_sector, uint32_t *out_offset);
void fat32_set_root_volume(void *fs_private) {
root_volume = (FAT32_Volume*)fs_private;
}
static void fat32_sync_if_root(FAT32_Volume *vol) {
if (!vol || vol != root_volume) return;
disk_sync(vol->disk);
}
// === Helper Functions (Shared) === // === Helper Functions (Shared) ===
@ -145,7 +161,7 @@ static void extract_parent_path(const char *path, char *parent) {
int i = len - 1; int i = len - 1;
while (i > 0 && path[i] == '/') i--; while (i > 0 && path[i] == '/') i--;
while (i > 0 && path[i] != '/') i--; while (i > 0 && path[i] != '/') i--;
if (i == 0) { if (i <= 0) {
parent[0] = '/'; parent[0] = '/';
parent[1] = 0; parent[1] = 0;
} else { } else {
@ -165,9 +181,12 @@ static char parse_drive_from_path(const char **path_ptr) {
*path_ptr = path + 2; *path_ptr = path + 2;
return drive; return drive;
} }
if (root_volume != NULL) return 0; // Pseudo-drive 0 means "use root volume"
return 'A'; // Default to RAMFS return 'A'; // Default to RAMFS
} }
// Normalize path (remove .., ., etc) // Normalize path (remove .., ., etc)
void fat32_normalize_path(const char *path, char *normalized) { void fat32_normalize_path(const char *path, char *normalized) {
char *temp = (char*)kmalloc(FAT32_MAX_PATH); char *temp = (char*)kmalloc(FAT32_MAX_PATH);
@ -467,6 +486,7 @@ static bool realfs_mount_volume(FAT32_Volume *vol, Disk *disk) {
vol->sectors_per_cluster = bpb->sectors_per_cluster; vol->sectors_per_cluster = bpb->sectors_per_cluster;
vol->root_cluster = bpb->root_cluster; vol->root_cluster = bpb->root_cluster;
vol->fat_size = bpb->sectors_per_fat_32; vol->fat_size = bpb->sectors_per_fat_32;
vol->num_fats = bpb->num_fats;
vol->total_sectors = bpb->total_sectors_32; vol->total_sectors = bpb->total_sectors_32;
vol->mounted = true; vol->mounted = true;
vol->cached_fat_sector = 0xFFFFFFFF; vol->cached_fat_sector = 0xFFFFFFFF;
@ -531,8 +551,32 @@ static uint32_t realfs_next_cluster(FAT32_Volume *vol, uint32_t cluster) {
return next; return next;
} }
static void realfs_set_fat_entry(FAT32_Volume *vol, uint32_t cluster, uint32_t value) {
uint32_t offset = cluster * 4;
uint32_t sector_offset = offset / 512;
uint32_t byte_offset = offset % 512;
uint8_t *buf = (uint8_t*)kmalloc(512);
if (!buf) return;
for (uint32_t i = 0; i < vol->num_fats; i++) {
uint32_t sector = vol->fat_begin_lba + (i * vol->fat_size) + sector_offset;
if (vol->disk->read_sector(vol->disk, sector, buf) == 0) {
uint32_t *entry = (uint32_t*)(buf + byte_offset);
*entry = (*entry & 0xF0000000) | (value & 0x0FFFFFFF);
vol->disk->write_sector(vol->disk, sector, buf);
if (vol->cached_fat_sector == sector) vol->cached_fat_sector = 0xFFFFFFFF;
}
}
kfree(buf);
}
static int realfs_read_cluster(FAT32_Volume *vol, uint32_t cluster, uint8_t *buffer) { static int realfs_read_cluster(FAT32_Volume *vol, uint32_t cluster, uint8_t *buffer) {
if (!vol || cluster < 2) return -1;
uint32_t lba = vol->cluster_begin_lba + (cluster - 2) * vol->sectors_per_cluster; uint32_t lba = vol->cluster_begin_lba + (cluster - 2) * vol->sectors_per_cluster;
if (vol->disk->read_sectors) {
return vol->disk->read_sectors(vol->disk, lba, vol->sectors_per_cluster, buffer);
}
for (uint32_t i = 0; i < vol->sectors_per_cluster; i++) { for (uint32_t i = 0; i < vol->sectors_per_cluster; i++) {
if (vol->disk->read_sector(vol->disk, lba + i, buffer + (i * 512)) != 0) return -1; if (vol->disk->read_sector(vol->disk, lba + i, buffer + (i * 512)) != 0) return -1;
} }
@ -550,7 +594,7 @@ static void to_dos_filename(const char *filename, char *out) {
if (name_len > 8) name_len = 8; if (name_len > 8) name_len = 8;
for (int i = 0; i < name_len; i++) { for (int i = 0; i < name_len; i++) {
char c = filename[i]; char c = filename[i];
// Preserve case - don't convert to uppercase if (c >= 'a' && c <= 'z') c -= 32;
out[i] = c; out[i] = c;
} }
if (dot != -1) { if (dot != -1) {
@ -558,12 +602,11 @@ static void to_dos_filename(const char *filename, char *out) {
if (ext_len > 3) ext_len = 3; if (ext_len > 3) ext_len = 3;
for (int i = 0; i < ext_len; i++) { for (int i = 0; i < ext_len; i++) {
char c = filename[dot + 1 + i]; char c = filename[dot + 1 + i];
// Preserve case - don't convert to uppercase if (c >= 'a' && c <= 'z') c -= 32;
out[8 + i] = c; out[8 + i] = c;
} }
} }
} }
static uint8_t fat_lfn_checksum(const uint8_t *short_name) { static uint8_t fat_lfn_checksum(const uint8_t *short_name) {
uint8_t sum = 0; uint8_t sum = 0;
for (int i = 11; i > 0; i--) { for (int i = 11; i > 0; i--) {
@ -572,6 +615,91 @@ static uint8_t fat_lfn_checksum(const uint8_t *short_name) {
return sum; return sum;
} }
static bool realfs_create_entry(FAT32_Volume *vol, uint32_t parent_cluster, const char *name, uint8_t attributes, uint32_t start_cluster, uint32_t file_size, uint32_t *out_sector, uint32_t *out_offset) {
char dos_name[11];
to_dos_filename(name, dos_name);
int name_len = fs_strlen(name);
bool needs_lfn = false;
int dot_pos = -1;
for (int i = 0; i < name_len; i++) {
if (name[i] == '.') { dot_pos = i; break; }
if (name[i] >= 'a' && name[i] <= 'z') needs_lfn = true;
}
if (!needs_lfn) {
if (dot_pos == -1) needs_lfn = (name_len > 8);
else needs_lfn = (dot_pos > 8) || (name_len - dot_pos - 1 > 3);
}
if (!needs_lfn && dot_pos != -1) {
for (int i = dot_pos + 1; i < name_len; i++) {
if (name[i] >= 'a' && name[i] <= 'z') { needs_lfn = true; break; }
}
}
int lfn_entries = needs_lfn ? ((name_len + 12) / 13) : 0;
int total_entries = lfn_entries + 1;
uint32_t free_cluster = 0;
int start_idx = -1;
if (!realfs_find_contiguous_free(vol, parent_cluster, total_entries, &free_cluster, &start_idx)) {
return false;
}
uint8_t *buf = (uint8_t*)kmalloc(vol->sectors_per_cluster * 512);
if (!buf) return false;
if (realfs_read_cluster(vol, free_cluster, buf) != 0) {
kfree(buf);
return false;
}
FAT32_DirEntry *entries = (FAT32_DirEntry*)buf;
uint8_t checksum = fat_lfn_checksum((uint8_t*)dos_name);
for (int i = 0; i < lfn_entries; i++) {
FAT32_LFNEntry *lfn = (FAT32_LFNEntry*)&entries[start_idx + i];
lfn->order = (lfn_entries - i);
if (i == 0) lfn->order |= 0x40;
lfn->attr = ATTR_LFN;
lfn->type = 0;
lfn->checksum = checksum;
lfn->first_cluster = 0;
int char_offset = (lfn_entries - i - 1) * 13;
for (int k = 0; k < 13; k++) {
uint16_t c = 0xFFFF;
if (char_offset + k < name_len) c = name[char_offset + k];
else if (char_offset + k == name_len) c = 0x0000;
if (k < 5) lfn->name1[k] = c;
else if (k < 11) lfn->name2[k-5] = c;
else lfn->name3[k-11] = c;
}
}
FAT32_DirEntry *d = &entries[start_idx + lfn_entries];
for (int k = 0; k < 8; k++) d->filename[k] = dos_name[k];
for (int k = 0; k < 3; k++) d->extension[k] = dos_name[8+k];
d->attributes = attributes;
d->start_cluster_high = (start_cluster >> 16);
d->start_cluster_low = (start_cluster & 0xFFFF);
d->file_size = file_size;
if (realfs_write_cluster(vol, free_cluster, buf) != 0) {
kfree(buf);
return false;
}
uint32_t lba = vol->cluster_begin_lba + (free_cluster - 2) * vol->sectors_per_cluster;
*out_sector = lba + ((start_idx + lfn_entries) * 32) / 512;
*out_offset = ((start_idx + lfn_entries) * 32) % 512;
kfree(buf);
return true;
}
static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *path, const char *mode) { static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *path, const char *mode) {
if (!vol || !vol->mounted) return NULL; if (!vol || !vol->mounted) return NULL;
@ -612,6 +740,10 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
uint32_t entry_sector = 0; uint32_t entry_sector = 0;
uint32_t entry_offset = 0; uint32_t entry_offset = 0;
uint8_t *cluster_buf = (uint8_t*)kmalloc(vol->sectors_per_cluster * 512);
if (!cluster_buf) return NULL;
uint8_t *lfn_cl_buf = NULL;
while (*p) { while (*p) {
// Extract component // Extract component
int i = 0; int i = 0;
@ -624,8 +756,6 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
// Search in current_cluster // Search in current_cluster
found = false; found = false;
uint32_t search_cluster = current_cluster; uint32_t search_cluster = current_cluster;
uint8_t *cluster_buf = (uint8_t*)kmalloc(vol->sectors_per_cluster * 512);
if (!cluster_buf) return NULL;
char lfn_buffer[256]; char lfn_buffer[256];
bool has_lfn = false; bool has_lfn = false;
@ -721,85 +851,7 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
if (!found) { if (!found) {
if ((mode[0] == 'w' || mode[0] == 'a') && *p == 0) { if ((mode[0] == 'w' || mode[0] == 'a') && *p == 0) {
// Create file logic with LFN support if (realfs_create_entry(vol, current_cluster, component, ATTR_ARCHIVE, 0, 0, &entry_sector, &entry_offset)) {
char dos_name[11];
to_dos_filename(component, dos_name);
int name_len = fs_strlen(component);
// Determine if LFN is needed:
// LFN is needed if the name contains characters that don't fit in 8.3 format
// or if the extension is longer than 3 chars or name part is longer than 8 chars
bool needs_lfn = false;
int dot_pos = -1;
for (int i = 0; i < name_len; i++) {
if (component[i] == '.') { dot_pos = i; break; }
}
if (dot_pos == -1) {
// No extension - need LFN if name > 8 chars
needs_lfn = (name_len > 8);
} else {
// Has extension
int name_part = dot_pos;
int ext_part = name_len - dot_pos - 1;
// Need LFN if name > 8 chars or extension > 3 chars
needs_lfn = (name_part > 8) || (ext_part > 3);
}
int lfn_entries = needs_lfn ? ((name_len + 12) / 13) : 0;
int total_entries = lfn_entries + 1;
uint32_t free_cluster = 0;
int start_entry_idx = -1;
uint8_t *lfn_cl_buf = (uint8_t*)kmalloc(vol->sectors_per_cluster * 512);
if (lfn_cl_buf && realfs_find_contiguous_free(vol, current_cluster, total_entries, &free_cluster, &start_entry_idx)) {
if (realfs_read_cluster(vol, free_cluster, lfn_cl_buf) == 0) {
FAT32_DirEntry *entries = (FAT32_DirEntry*)lfn_cl_buf;
uint8_t checksum = fat_lfn_checksum((uint8_t*)dos_name);
// Write LFN entries in reverse order
for (int i = 0; i < lfn_entries; i++) {
FAT32_LFNEntry *lfn = (FAT32_LFNEntry*)&entries[start_entry_idx + i];
lfn->order = (lfn_entries - i);
if (i == 0) lfn->order |= 0x40; // Last logical LFN entry
lfn->attr = ATTR_LFN;
lfn->type = 0;
lfn->checksum = checksum;
lfn->first_cluster = 0;
// Fill name chars (13 chars per entry)
int char_offset = (lfn_entries - i - 1) * 13;
for (int k = 0; k < 13; k++) {
uint16_t c = 0xFFFF;
if (char_offset + k < name_len) c = component[char_offset + k];
else if (char_offset + k == name_len) c = 0x0000;
if (k < 5) lfn->name1[k] = c;
else if (k < 11) lfn->name2[k-5] = c;
else lfn->name3[k-11] = c;
}
}
// Write 8.3 entry
FAT32_DirEntry *d = &entries[start_entry_idx + lfn_entries];
for(int k=0; k<8; k++) d->filename[k] = dos_name[k];
for(int k=0; k<3; k++) d->extension[k] = dos_name[8+k];
d->attributes = ATTR_ARCHIVE;
d->start_cluster_high = 0;
d->start_cluster_low = 0;
d->file_size = 0;
if (realfs_write_cluster(vol, free_cluster, lfn_cl_buf) == 0) {
uint32_t lba = vol->cluster_begin_lba + (free_cluster - 2) * vol->sectors_per_cluster;
entry_sector = lba + ((start_entry_idx + lfn_entries) * 32) / 512;
entry_offset = ((start_entry_idx + lfn_entries) * 32) % 512;
kfree(lfn_cl_buf);
if (cluster_buf) kfree(cluster_buf);
FAT32_FileHandle *fh = ramfs_find_free_handle(); FAT32_FileHandle *fh = ramfs_find_free_handle();
if (fh) { if (fh) {
fh->valid = true; fh->valid = true;
@ -813,21 +865,20 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
fh->attributes = ATTR_ARCHIVE; fh->attributes = ATTR_ARCHIVE;
fh->dir_sector = entry_sector; fh->dir_sector = entry_sector;
fh->dir_offset = entry_offset; fh->dir_offset = entry_offset;
if (cluster_buf) kfree(cluster_buf);
return fh; return fh;
} }
} }
}
}
if (lfn_cl_buf) kfree(lfn_cl_buf);
if (cluster_buf) kfree(cluster_buf); if (cluster_buf) kfree(cluster_buf);
return NULL; return NULL;
} }
kfree(cluster_buf); if (cluster_buf) { kfree(cluster_buf); cluster_buf = NULL; }
return NULL; return NULL;
} }
kfree(cluster_buf);
} }
// Found file/dir // Found file/dir
FAT32_FileHandle *fh = ramfs_find_free_handle(); FAT32_FileHandle *fh = ramfs_find_free_handle();
if (fh) { if (fh) {
@ -843,6 +894,10 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
fh->dir_sector = entry_sector; fh->dir_sector = entry_sector;
fh->dir_offset = entry_offset; fh->dir_offset = entry_offset;
if (mode[0] == 'w' && !is_directory) {
handle_fat32_truncate(fh);
}
if (mode[0] == 'a') { if (mode[0] == 'a') {
fh->position = fh->size; fh->position = fh->size;
// Seek to EOF cluster // Seek to EOF cluster
@ -855,10 +910,10 @@ static FAT32_FileHandle* realfs_open_from_vol(FAT32_Volume *vol, const char *pat
pos += cluster_size; pos += cluster_size;
} }
} }
return fh;
} }
if (cluster_buf) kfree(cluster_buf);
return NULL; if (lfn_cl_buf) kfree(lfn_cl_buf);
return fh;
} }
static int realfs_read_file(FAT32_FileHandle *handle, void *buffer, int size, uint8_t *ext_cluster_buf) { static int realfs_read_file(FAT32_FileHandle *handle, void *buffer, int size, uint8_t *ext_cluster_buf) {
@ -905,8 +960,11 @@ static int realfs_read_file(FAT32_FileHandle *handle, void *buffer, int size, ui
} }
static int realfs_write_cluster(FAT32_Volume *vol, uint32_t cluster, const uint8_t *buffer) { static int realfs_write_cluster(FAT32_Volume *vol, uint32_t cluster, const uint8_t *buffer) {
if (cluster < 2 || cluster >= 0x0FFFFFF8) return -1; if (!vol || cluster < 2 || cluster >= 0x0FFFFFF8) return -1;
uint32_t lba = vol->cluster_begin_lba + (cluster - 2) * vol->sectors_per_cluster; uint32_t lba = vol->cluster_begin_lba + (cluster - 2) * vol->sectors_per_cluster;
if (vol->disk->write_sectors) {
return vol->disk->write_sectors(vol->disk, lba, vol->sectors_per_cluster, buffer);
}
for (uint32_t i = 0; i < vol->sectors_per_cluster; i++) { for (uint32_t i = 0; i < vol->sectors_per_cluster; i++) {
if (vol->disk->write_sector(vol->disk, lba + i, buffer + (i * 512)) != 0) return -1; if (vol->disk->write_sector(vol->disk, lba + i, buffer + (i * 512)) != 0) return -1;
} }
@ -988,13 +1046,11 @@ static bool realfs_find_contiguous_free(FAT32_Volume *vol, uint32_t dir_start_cl
static uint32_t realfs_allocate_cluster(FAT32_Volume *vol) { static uint32_t realfs_allocate_cluster(FAT32_Volume *vol) {
uint32_t current = vol->last_allocated_cluster; uint32_t current = vol->last_allocated_cluster;
if (current < 2) current = 2; if (current < 3) current = 3;
uint32_t fat_entries = (vol->fat_size * 512) / 4; uint32_t fat_entries = (vol->fat_size * 512) / 4;
uint32_t first_search = current; uint32_t first_search = current;
// Skip cluster 2 as it's reserved for the root directory in FAT32
if (current == vol->root_cluster) current++;
uint8_t *fat_buf = (uint8_t*)kmalloc(512); uint8_t *fat_buf = (uint8_t*)kmalloc(512);
if (!fat_buf) return 0; if (!fat_buf) return 0;
@ -1011,13 +1067,9 @@ static uint32_t realfs_allocate_cluster(FAT32_Volume *vol) {
uint32_t val = *(uint32_t*)&fat_buf[offset]; uint32_t val = *(uint32_t*)&fat_buf[offset];
if ((val & 0x0FFFFFFF) == 0) { if ((val & 0x0FFFFFFF) == 0) {
*(uint32_t*)&fat_buf[offset] = 0x0FFFFFFF; // EOC
vol->disk->write_sector(vol->disk, sector, fat_buf);
if (vol->cached_fat_sector == sector) {
vol->cached_fat_sector = 0xFFFFFFFF;
}
vol->last_allocated_cluster = current;
kfree(fat_buf); kfree(fat_buf);
realfs_set_fat_entry(vol, current, 0x0FFFFFFF); // EOC
vol->last_allocated_cluster = current;
return current; return current;
} }
current++; current++;
@ -1028,6 +1080,32 @@ static uint32_t realfs_allocate_cluster(FAT32_Volume *vol) {
return 0; // Full return 0; // Full
} }
static void realfs_free_cluster_chain(FAT32_Volume *vol, uint32_t start_cluster) {
if (start_cluster == 0 || start_cluster >= 0x0FFFFFF8) return;
uint32_t current = start_cluster;
while (current < 0x0FFFFFF8 && current >= 2) {
uint32_t next = realfs_next_cluster(vol, current);
realfs_set_fat_entry(vol, current, 0);
if (next == current) break;
current = next;
}
}
static void handle_fat32_truncate(FAT32_FileHandle *handle) {
FAT32_Volume *vol = (FAT32_Volume*)handle->volume;
if (!vol || handle->start_cluster == 0) return;
uint32_t start = handle->start_cluster;
handle->start_cluster = 0;
handle->cluster = 0;
handle->size = 0;
handle->position = 0;
realfs_free_cluster_chain(vol, start);
realfs_update_dir_entry_size(vol, handle);
}
static int realfs_write_file(FAT32_FileHandle *handle, const void *buffer, int size, uint8_t *ext_cluster_buf) { static int realfs_write_file(FAT32_FileHandle *handle, const void *buffer, int size, uint8_t *ext_cluster_buf) {
FAT32_Volume *vol = (FAT32_Volume*)handle->volume; FAT32_Volume *vol = (FAT32_Volume*)handle->volume;
if (!vol) return 0; if (!vol) return 0;
@ -1070,32 +1148,25 @@ static int realfs_write_file(FAT32_FileHandle *handle, const void *buffer, int s
uint32_t new_cluster = realfs_allocate_cluster(vol); uint32_t new_cluster = realfs_allocate_cluster(vol);
if (new_cluster == 0) break; if (new_cluster == 0) break;
uint32_t fs = vol->fat_begin_lba + (handle->cluster * 4) / 512; realfs_set_fat_entry(vol, handle->cluster, new_cluster);
uint32_t fo = (handle->cluster * 4) % 512;
uint8_t fbuf[512];
if (vol->disk->read_sector(vol->disk, fs, fbuf) == 0) {
uint32_t old = *(uint32_t*)&fbuf[fo];
*(uint32_t*)&fbuf[fo] = (old & 0xF0000000) | (new_cluster & 0x0FFFFFFF);
vol->disk->write_sector(vol->disk, fs, fbuf);
if (vol->cached_fat_sector == fs) vol->cached_fat_sector = 0xFFFFFFFF;
}
next = new_cluster; next = new_cluster;
} }
handle->cluster = next; handle->cluster = next;
} }
// Always zero the buffer first to ensure clean state
for (int i = 0; i < (int)cluster_size; i++) cluster_buf[i] = 0;
// If we're in the middle of a cluster, read the existing data first
if (offset > 0 || (handle->position < handle->size)) {
if (realfs_read_cluster(vol, handle->cluster, cluster_buf) != 0) break;
}
int to_copy = size - bytes_written; int to_copy = size - bytes_written;
int available = cluster_size - offset; int available = cluster_size - offset;
if (to_copy > available) to_copy = available; if (to_copy > available) to_copy = available;
if (offset > 0 || (handle->position < handle->size && (handle->position + to_copy) < handle->size)) {
if (realfs_read_cluster(vol, handle->cluster, cluster_buf) != 0) break;
} else {
if (to_copy < (int)cluster_size) {
for (int i = 0; i < (int)cluster_size; i++) cluster_buf[i] = 0;
}
}
// Copy new data into the cluster buffer // Copy new data into the cluster buffer
for (int i = 0; i < to_copy; i++) { for (int i = 0; i < to_copy; i++) {
cluster_buf[offset + i] = src_buf[bytes_written + i]; cluster_buf[offset + i] = src_buf[bytes_written + i];
@ -1244,17 +1315,10 @@ static bool realfs_delete_from_vol(FAT32_Volume *vol, const char *path) {
} }
// Mark the main entry as deleted // Mark the main entry as deleted
entry[e].filename[0] = 0xE5; entry[e].filename[0] = 0xE5;
// Persist the changes to disk
// CRITICAL FIX: Write ALL sectors that contain modified entries
// LFN entries and main entry may span multiple sectors in the cluster
uint32_t lba = vol->cluster_begin_lba + (search_cluster - 2) * vol->sectors_per_cluster; uint32_t lba = vol->cluster_begin_lba + (search_cluster - 2) * vol->sectors_per_cluster;
// Find all sectors touched by LFN and main entries uint8_t sectors_to_write[8] = {0};
uint8_t sectors_to_write[8] = {0}; // Max 8 sectors per cluster
int num_sectors = 0; int num_sectors = 0;
// Mark sectors containing LFN entries
if (lfn_start_entry != -1) { if (lfn_start_entry != -1) {
for (int k = lfn_start_entry; k < e; k++) { for (int k = lfn_start_entry; k < e; k++) {
int sect_idx = (k * 32) / 512; int sect_idx = (k * 32) / 512;
@ -1312,30 +1376,8 @@ static bool realfs_delete_from_vol(FAT32_Volume *vol, const char *path) {
if (*p == 0) break; // End of path if (*p == 0) break; // End of path
} }
// 2. Free all clusters used by the file realfs_free_cluster_chain(vol, file_start_cluster);
if (file_start_cluster != 0 && file_start_cluster < 0x0FFFFFF8) {
uint32_t current = file_start_cluster;
while (current < 0x0FFFFFF8) {
uint32_t next = realfs_next_cluster(vol, current);
// Mark this cluster as free in FAT
uint32_t fat_sector = vol->fat_begin_lba + (current * 4) / 512;
uint32_t fat_offset = (current * 4) % 512;
uint8_t *fat_buf = (uint8_t*)kmalloc(512);
if (!fat_buf) break;
if (vol->disk->read_sector(vol->disk, fat_sector, fat_buf) == 0) {
*(uint32_t*)&fat_buf[fat_offset] = 0; // Free
vol->disk->write_sector(vol->disk, fat_sector, fat_buf);
if (vol->cached_fat_sector == fat_sector) vol->cached_fat_sector = 0xFFFFFFFF;
}
kfree(fat_buf);
current = next;
}
}
kfree(cluster_buf); kfree(cluster_buf);
return true; return true;
} }
@ -1389,6 +1431,7 @@ static int realfs_list_directory_vol(FAT32_Volume *vol, const char *path, FAT32_
for (int e = 0; e < (int)((vol->sectors_per_cluster*512)/32) && count < max_entries; e++) { for (int e = 0; e < (int)((vol->sectors_per_cluster*512)/32) && count < max_entries; e++) {
if (de[e].filename[0] == 0) { current_cluster = 0xFFFFFFFF; break; } if (de[e].filename[0] == 0) { current_cluster = 0xFFFFFFFF; break; }
if (de[e].filename[0] == 0xE5) { has_lfn = false; continue; } if (de[e].filename[0] == 0xE5) { has_lfn = false; continue; }
if (de[e].filename[0] == 0x2E) { has_lfn = false; continue; } // Skip . and ..
if (de[e].attributes == ATTR_LFN) { if (de[e].attributes == ATTR_LFN) {
FAT32_LFNEntry *l = (FAT32_LFNEntry*)&de[e]; FAT32_LFNEntry *l = (FAT32_LFNEntry*)&de[e];
if (l->order & 0x40) for(int k=0; k<256; k++) lfn_buffer[k] = 0; if (l->order & 0x40) for(int k=0; k<256; k++) lfn_buffer[k] = 0;
@ -1618,6 +1661,22 @@ static uint32_t vfs_fat_get_size(void *file_handle) {
return ((FAT32_FileHandle*)file_handle)->size; return ((FAT32_FileHandle*)file_handle)->size;
} }
static int vfs_ramfs_statfs(void *fs_private, vfs_statfs_t *stat) {
(void)fs_private;
uint64_t rflags = spinlock_acquire_irqsave(&ramfs_lock);
stat->total_blocks = MAX_CLUSTERS;
uint64_t free_count = 0;
for (int i = 0; i < MAX_CLUSTERS; i++) {
if (fat_table[i] == 0) free_count++;
}
stat->free_blocks = free_count;
stat->block_size = FAT32_CLUSTER_SIZE;
spinlock_release_irqrestore(&ramfs_lock, rflags);
return 0;
}
static struct vfs_fs_ops ramfs_ops = { static struct vfs_fs_ops ramfs_ops = {
.open = vfs_ramfs_open, .open = vfs_ramfs_open,
.close = vfs_ramfs_close, .close = vfs_ramfs_close,
@ -1633,7 +1692,8 @@ static struct vfs_fs_ops ramfs_ops = {
.is_dir = vfs_ramfs_is_dir, .is_dir = vfs_ramfs_is_dir,
.get_info = vfs_ramfs_get_info, .get_info = vfs_ramfs_get_info,
.get_position = vfs_fat_get_position, .get_position = vfs_fat_get_position,
.get_size = vfs_fat_get_size .get_size = vfs_fat_get_size,
.statfs = vfs_ramfs_statfs
}; };
struct vfs_fs_ops* fat32_get_ramfs_ops(void) { struct vfs_fs_ops* fat32_get_ramfs_ops(void) {
@ -1707,6 +1767,9 @@ static int vfs_realfs_write(void *fs_private, void *file_handle, const void *buf
if (ret < to_write) break; if (ret < to_write) break;
} }
if (total_written > 0) {
fat32_sync_if_root(vol);
}
kfree(cluster_buf); kfree(cluster_buf);
return total_written; return total_written;
} }
@ -1741,6 +1804,7 @@ static bool vfs_realfs_mkdir(void *fs_private, const char *rel_path) {
uint64_t rflags = spinlock_acquire_irqsave(&vol->lock); uint64_t rflags = spinlock_acquire_irqsave(&vol->lock);
bool ret = realfs_mkdir_vol((FAT32_Volume*)fs_private, rel_path); bool ret = realfs_mkdir_vol((FAT32_Volume*)fs_private, rel_path);
spinlock_release_irqrestore(&vol->lock, rflags); spinlock_release_irqrestore(&vol->lock, rflags);
if (ret) fat32_sync_if_root(vol);
return ret; return ret;
} }
@ -1754,6 +1818,7 @@ static bool vfs_realfs_unlink(void *fs_private, const char *rel_path) {
uint64_t rflags = spinlock_acquire_irqsave(&vol->lock); uint64_t rflags = spinlock_acquire_irqsave(&vol->lock);
bool ret = realfs_delete_from_vol((FAT32_Volume*)fs_private, rel_path); bool ret = realfs_delete_from_vol((FAT32_Volume*)fs_private, rel_path);
spinlock_release_irqrestore(&vol->lock, rflags); spinlock_release_irqrestore(&vol->lock, rflags);
if (ret) fat32_sync_if_root(vol);
return ret; return ret;
} }
@ -1806,10 +1871,49 @@ static int vfs_realfs_get_info(void *fs_private, const char *rel_path, vfs_diren
spinlock_release_irqrestore(&vol->lock, rflags); spinlock_release_irqrestore(&vol->lock, rflags);
return 0; return 0;
} }
spinlock_release_irqrestore(&ramfs_lock, rflags); spinlock_release_irqrestore(&vol->lock, rflags);
return -1; return -1;
} }
static int vfs_realfs_statfs(void *fs_private, vfs_statfs_t *stat) {
FAT32_Volume *vol = (FAT32_Volume*)fs_private;
uint64_t rflags = spinlock_acquire_irqsave(&vol->lock);
stat->total_blocks = vol->total_sectors / vol->sectors_per_cluster;
stat->block_size = vol->sectors_per_cluster * 512;
// Instead of scanning the entire FAT which can be slow,
// we estimate or count a subset, but let's do a fast count.
uint64_t free_count = 0;
uint32_t fat_entries = (vol->fat_size * 512) / 4;
uint32_t current = 2;
uint8_t *fat_buf = (uint8_t*)kmalloc(512);
if (fat_buf) {
uint32_t cached_sector = 0xFFFFFFFF;
while (current < fat_entries) {
uint32_t sector = vol->fat_begin_lba + (current * 4) / 512;
uint32_t offset = (current * 4) % 512;
if (sector != cached_sector) {
if (vol->disk->read_sector(vol->disk, sector, fat_buf) != 0) break;
cached_sector = sector;
}
uint32_t val = *(uint32_t*)&fat_buf[offset];
if ((val & 0x0FFFFFFF) == 0) free_count++;
current++;
}
kfree(fat_buf);
}
stat->free_blocks = free_count;
spinlock_release_irqrestore(&vol->lock, rflags);
return 0;
}
static struct vfs_fs_ops realfs_ops = { static struct vfs_fs_ops realfs_ops = {
.open = vfs_realfs_open, .open = vfs_realfs_open,
.close = vfs_realfs_close, .close = vfs_realfs_close,
@ -1825,7 +1929,8 @@ static struct vfs_fs_ops realfs_ops = {
.is_dir = vfs_realfs_is_dir, .is_dir = vfs_realfs_is_dir,
.get_info = vfs_realfs_get_info, .get_info = vfs_realfs_get_info,
.get_position = vfs_fat_get_position, .get_position = vfs_fat_get_position,
.get_size = vfs_fat_get_size .get_size = vfs_fat_get_size,
.statfs = vfs_realfs_statfs
}; };
struct vfs_fs_ops* fat32_get_realfs_ops(void) { struct vfs_fs_ops* fat32_get_realfs_ops(void) {
@ -1915,8 +2020,9 @@ FAT32_FileHandle* fat32_open_nolock(const char *path, const char *mode) {
fat32_normalize_path(p, normalized); fat32_normalize_path(p, normalized);
handle = ramfs_open(normalized, mode); handle = ramfs_open(normalized, mode);
kfree(normalized); kfree(normalized);
} else if (drive == 0) {
handle = realfs_open_from_vol(root_volume, p, mode);
} else if (drive != 0) { } else if (drive != 0) {
// Find volume by drive letter (B=0, C=1...)
Disk *d = disk_get_by_letter(drive); Disk *d = disk_get_by_letter(drive);
if (d) { if (d) {
for (int i = 0; i < real_volume_count; i++) { for (int i = 0; i < real_volume_count; i++) {
@ -1962,6 +2068,7 @@ void fat32_close_nolock(FAT32_FileHandle *handle) {
kfree(buf); kfree(buf);
} }
} }
fat32_sync_if_root(vol);
} }
handle->valid = false; handle->valid = false;
} }
@ -2005,6 +2112,10 @@ int fat32_write(FAT32_FileHandle *handle, const void *buffer, int size) {
ret = ramfs_write(handle, buffer, size); ret = ramfs_write(handle, buffer, size);
} else { } else {
ret = realfs_write_file(handle, buffer, size, NULL); ret = realfs_write_file(handle, buffer, size, NULL);
if (ret > 0) {
FAT32_Volume *vol = (FAT32_Volume*)handle->volume;
fat32_sync_if_root(vol);
}
} }
spinlock_release_irqrestore(&ramfs_lock, rflags); spinlock_release_irqrestore(&ramfs_lock, rflags);
@ -2070,8 +2181,12 @@ static bool realfs_mkdir_vol(FAT32_Volume *vol, const char *path) {
// Open parent directory // Open parent directory
FAT32_FileHandle *parent_fh = realfs_open_from_vol(vol, parent_path, "r"); FAT32_FileHandle *parent_fh = realfs_open_from_vol(vol, parent_path, "r");
kfree(parent_path); kfree(parent_path);
if (!parent_fh) return false; if (!parent_fh) {
serial_write("[FAT32] mkdir ERROR: parent not found\n");
return false;
}
uint32_t parent_cluster = parent_fh->start_cluster; uint32_t parent_cluster = parent_fh->start_cluster;
extern void fat32_close_nolock(FAT32_FileHandle *handle); extern void fat32_close_nolock(FAT32_FileHandle *handle);
fat32_close_nolock(parent_fh); fat32_close_nolock(parent_fh);
@ -2117,73 +2232,14 @@ static bool realfs_mkdir_vol(FAT32_Volume *vol, const char *path) {
} }
kfree(cluster_buf); kfree(cluster_buf);
// Find free entry in parent directory
uint32_t search_cluster = parent_cluster;
uint8_t *sect_buf = (uint8_t*)kmalloc(512);
bool found_free = false;
uint32_t free_sector = 0; uint32_t free_sector = 0;
uint32_t free_offset = 0; uint32_t free_offset = 0;
uint8_t *dir_cluster_buf = (uint8_t*)kmalloc(cluster_size); if (!realfs_create_entry(vol, parent_cluster, dirname, ATTR_DIRECTORY, new_cluster, 0, &free_sector, &free_offset)) {
if (!dir_cluster_buf) {
kfree(sect_buf);
return false; return false;
} }
while (search_cluster < 0x0FFFFFF8 && !found_free) { return true;
if (realfs_read_cluster(vol, search_cluster, dir_cluster_buf) != 0) break;
FAT32_DirEntry *entries = (FAT32_DirEntry*)dir_cluster_buf;
int count = cluster_size / 32;
for (int e = 0; e < count; e++) {
if (entries[e].filename[0] == 0 || entries[e].filename[0] == 0xE5) {
uint32_t lba = vol->cluster_begin_lba + (search_cluster - 2) * vol->sectors_per_cluster;
free_sector = lba + (e * 32) / 512;
free_offset = (e * 32) % 512;
found_free = true;
break;
}
}
if (!found_free) {
uint32_t next = realfs_next_cluster(vol, search_cluster);
if (next >= 0x0FFFFFF8) {
// Need to expand directory?
uint32_t expanded = realfs_allocate_cluster(vol);
if (expanded == 0) break;
// Link, preserve high bits
uint32_t fat_sector = vol->fat_begin_lba + (search_cluster * 4) / 512;
uint32_t fat_offset = (search_cluster * 4) % 512;
if (vol->disk->read_sector(vol->disk, fat_sector, sect_buf) == 0) {
uint32_t old_val = *(uint32_t*)&sect_buf[fat_offset];
*(uint32_t*)&sect_buf[fat_offset] = (old_val & 0xF0000000) | (expanded & 0x0FFFFFFF);
vol->disk->write_sector(vol->disk, fat_sector, sect_buf);
if (vol->cached_fat_sector == fat_sector) vol->cached_fat_sector = 0xFFFFFFFF;
}
// Zero out new cluster
for(uint32_t k=0; k<cluster_size; k++) dir_cluster_buf[k] = 0;
realfs_write_cluster(vol, expanded, dir_cluster_buf);
next = expanded;
}
search_cluster = next;
}
}
kfree(dir_cluster_buf);
if (found_free) {
vol->disk->read_sector(vol->disk, free_sector, sect_buf);
FAT32_DirEntry *d = (FAT32_DirEntry*)(sect_buf + free_offset);
char dos_name[11];
to_dos_filename(dirname, dos_name);
for (int k = 0; k < 8; k++) d->filename[k] = dos_name[k];
for (int k = 0; k < 3; k++) d->extension[k] = dos_name[8+k];
d->attributes = ATTR_DIRECTORY;
d->start_cluster_high = (new_cluster >> 16);
d->start_cluster_low = (new_cluster & 0xFFFF);
d->file_size = 0;
vol->disk->write_sector(vol->disk, free_sector, sect_buf);
}
kfree(sect_buf);
return found_free;
} }
bool fat32_mkdir(const char *path) { bool fat32_mkdir(const char *path) {
@ -2193,7 +2249,12 @@ bool fat32_mkdir(const char *path) {
// SMP: Use FAT32 spinlock // SMP: Use FAT32 spinlock
uint64_t rflags = spinlock_acquire_irqsave(&ramfs_lock); uint64_t rflags = spinlock_acquire_irqsave(&ramfs_lock);
if (drive != 'A') { if (drive == 0) {
bool res = realfs_mkdir_vol(root_volume, p);
wm_notify_fs_change();
spinlock_release_irqrestore(&ramfs_lock, rflags);
return res;
} else if (drive != 'A') {
Disk *d = disk_get_by_letter(drive); Disk *d = disk_get_by_letter(drive);
if (d) { if (d) {
for (int i = 0; i < real_volume_count; i++) { for (int i = 0; i < real_volume_count; i++) {
@ -2298,8 +2359,20 @@ bool fat32_delete(const char *path) {
kfree(normalized); kfree(normalized);
if (result) wm_notify_fs_change(); if (result) wm_notify_fs_change();
} }
} else if (drive == 0) {
result = realfs_delete_from_vol(root_volume, p);
if (result) wm_notify_fs_change();
} else { } else {
// Obsolete RealFS realfs_delete Disk *d = disk_get_by_letter(drive);
if (d) {
for (int i = 0; i < real_volume_count; i++) {
if (real_volumes[i]->disk == d) {
result = realfs_delete_from_vol(real_volumes[i], p);
if (result) wm_notify_fs_change();
break;
}
}
}
} }
spinlock_release_irqrestore(&ramfs_lock, rflags); spinlock_release_irqrestore(&ramfs_lock, rflags);
@ -2391,6 +2464,12 @@ bool fat32_exists(const char *path) {
exists = (ramfs_find_file(normalized) != NULL); exists = (ramfs_find_file(normalized) != NULL);
kfree(normalized); kfree(normalized);
} }
} else if (drive == 0) {
FAT32_FileHandle *fh = realfs_open_from_vol(root_volume, p, "r");
if (fh) {
exists = true;
fat32_close_nolock(fh);
}
} else { } else {
// RealFS check // RealFS check
Disk *d = disk_get_by_letter(drive); Disk *d = disk_get_by_letter(drive);
@ -2472,6 +2551,12 @@ bool fat32_is_directory_nolock(const char *path) {
is_dir = (entry && (entry->attributes & ATTR_DIRECTORY)); is_dir = (entry && (entry->attributes & ATTR_DIRECTORY));
kfree(normalized); kfree(normalized);
} }
} else if (drive == 0) {
FAT32_FileHandle *fh = realfs_open_from_vol(root_volume, p, "r");
if (fh) {
is_dir = fh->is_directory;
fat32_close_nolock(fh);
}
} else { } else {
Disk *d = disk_get_by_letter(drive); Disk *d = disk_get_by_letter(drive);
if (d) { if (d) {

View file

@ -163,6 +163,9 @@ char fat32_get_current_drive(void);
void fat32_normalize_path(const char *path, char *normalized); void fat32_normalize_path(const char *path, char *normalized);
bool fs_starts_with(const char *str, const char *prefix); bool fs_starts_with(const char *str, const char *prefix);
// Root volume sync helpers
void fat32_set_root_volume(void *fs_private);
// Desktop Limit // Desktop Limit
void fat32_set_desktop_limit(int limit); void fat32_set_desktop_limit(int limit);

297
src/fs/mkfs_fat32.c Normal file
View file

@ -0,0 +1,297 @@
// 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 "mkfs_fat32.h"
#include "../dev/disk.h"
#include <stddef.h>
#include "memory_manager.h"
extern void serial_write(const char *str);
extern void serial_write_num(uint64_t num);
// Internal helpers
static void mf_memset(void *dst, int val, int len) {
unsigned char *p = (unsigned char *)dst;
while (len-- > 0) *p++ = (unsigned char)val;
}
static void mf_memcpy(void *dst, const void *src, int len) {
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)src;
while (len-- > 0) *d++ = *s++;
}
static void mf_strncpy(char *dst, const char *src, int n) {
int i = 0;
while (i < n && src[i]) { dst[i] = src[i]; i++; }
while (i < n) { dst[i++] = ' '; } /* FAT labels are space-padded */
}
static void mf_set_disk_label(Disk *disk, const char *label) {
int end = 11;
while (end > 0 && label[end - 1] == ' ') end--;
for (int i = 0; i < end && i < 31; i++) disk->label[i] = label[i];
disk->label[end < 31 ? end : 31] = 0;
}
// On-disk BPB structures
typedef struct __attribute__((packed)) {
/* DOS 2.0 BPB */
uint8_t jump_boot[3]; /* EB 58 90 */
char oem_name[8]; /* "MSDOS5.0" */
uint16_t bytes_per_sector; /* 512 */
uint8_t sectors_per_cluster;
uint16_t reserved_sector_count;
uint8_t num_fats; /* 2 */
uint16_t root_entry_count; /* 0 for FAT32 */
uint16_t total_sectors_16; /* 0 for FAT32 */
uint8_t media; /* 0xF8 for fixed disk */
uint16_t fat_size_16; /* 0 for FAT32 */
uint16_t sectors_per_track;
uint16_t num_heads;
uint32_t hidden_sectors;
uint32_t total_sectors_32;
/* FAT32 extended BPB */
uint32_t fat_size_32;
uint16_t ext_flags;
uint16_t fs_version; /* 0x0000 */
uint32_t root_cluster; /* 2 */
uint16_t fs_info; /* sector 1 */
uint16_t backup_boot_sector; /* sector 6 */
uint8_t reserved[12];
uint8_t drive_number; /* 0x80 for fixed disk */
uint8_t reserved1;
uint8_t boot_sig; /* 0x29 */
uint32_t volume_id;
char volume_label[11]; /* space-padded */
char fs_type[8]; /* "FAT32 " */
uint8_t boot_code[420];
uint16_t boot_signature; /* 0xAA55 */
} FAT32_BPB;
typedef struct __attribute__((packed)) {
uint32_t lead_sig; /* 0x41615252 */
uint8_t reserved1[480];
uint32_t struct_sig; /* 0x61417272 */
uint32_t free_count; /* 0xFFFFFFFF = unknown */
uint32_t next_free; /* 0xFFFFFFFF = unknown */
uint8_t reserved2[12];
uint32_t trail_sig; /* 0xAA550000 */
} FAT32_FSInfo;
// Public API
int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label) {
if (sector_count < MIN_FAT32_SECTORS) {
serial_write("[MKFS] Error: partition too small for FAT32 (< 32 MB)\n");
return -1;
}
if (!disk || !disk->write_sector) {
serial_write("[MKFS] Error: null disk or no write function\n");
return -1;
}
uint8_t spc; /* sectors per cluster */
if (sector_count < 532480) spc = 1; /* < 260 MB -> 512 B clusters */
else if (sector_count < 1064960) spc = 2; /* < 520 MB -> 1 KB */
else if (sector_count < 2097152) spc = 4; /* < 1 GB -> 2 KB */
else if (sector_count < 16777216) spc = 8; /* < 8 GB -> 4 KB */
else if (sector_count < 33554432) spc = 16; /* < 16 GB -> 8 KB */
else if (sector_count < 67108864) spc = 32; /* < 32 GB -> 16 KB */
else spc = 64; /* >= 32 GB -> 32 KB */
const uint32_t reserved_sectors = 32;
const uint8_t num_fats = 2;
const uint32_t root_cluster = 2;
uint32_t data_sectors = sector_count - reserved_sectors;
uint32_t cluster_count = data_sectors / spc;
uint32_t fat_bytes = (cluster_count + 2) * 4;
uint32_t sectors_per_fat = (fat_bytes + 511) / 512;
uint8_t *buf = (uint8_t *)kmalloc(512);
if (!buf) {
serial_write("[MKFS] Error: out of memory\n");
return -1;
}
FAT32_BPB *bpb = (FAT32_BPB *)buf;
mf_memset(bpb, 0, 512);
bpb->jump_boot[0] = 0xEB;
bpb->jump_boot[1] = 0x58;
bpb->jump_boot[2] = 0x90;
mf_memcpy(bpb->oem_name, "MSDOS5.0", 8);
bpb->bytes_per_sector = 512;
bpb->sectors_per_cluster = spc;
bpb->reserved_sector_count = (uint16_t)reserved_sectors;
bpb->num_fats = num_fats;
bpb->root_entry_count = 0;
bpb->total_sectors_16 = 0;
bpb->media = 0xF8;
bpb->fat_size_16 = 0;
bpb->sectors_per_track = 63;
bpb->num_heads = 255;
bpb->hidden_sectors = disk->partition_lba_offset;
bpb->total_sectors_32 = sector_count;
bpb->fat_size_32 = sectors_per_fat;
bpb->ext_flags = 0;
bpb->fs_version = 0;
bpb->root_cluster = root_cluster;
bpb->fs_info = 1;
bpb->backup_boot_sector = 6;
bpb->drive_number = 0x80;
bpb->boot_sig = 0x29;
bpb->volume_id = 0x12345678; /* arbitrary non-zero volume ID */
/* Volume label */
const char *vol_label = (label && label[0]) ? label : "NO NAME ";
char upper_label[11];
for (int i = 0; i < 11; i++) {
if (vol_label[i] == 0) {
for (int j = i; j < 11; j++) upper_label[j] = ' ';
break;
}
char c = vol_label[i];
if (c >= 'a' && c <= 'z') c -= 32;
upper_label[i] = c;
}
mf_memcpy(bpb->volume_label, upper_label, 11);
mf_memcpy(bpb->fs_type, "FAT32 ", 8);
/* Boot sector signature */
bpb->boot_signature = 0xAA55;
/* Write sector 0 (BPB) */
if (disk->write_sector(disk, 0, buf) != 0) {
serial_write("[MKFS] Error: failed to write BPB (sector 0)\n");
kfree(buf);
return -1;
}
FAT32_FSInfo *fsinfo = (FAT32_FSInfo *)buf;
mf_memset(fsinfo, 0, 512);
fsinfo->lead_sig = 0x41615252;
fsinfo->struct_sig = 0x61417272;
fsinfo->free_count = 0xFFFFFFFF;
fsinfo->next_free = 0xFFFFFFFF;
fsinfo->trail_sig = 0xAA550000;
if (disk->write_sector(disk, 1, buf) != 0) {
serial_write("[MKFS] Error: failed to write FSInfo (sector 1)\n");
kfree(buf);
return -1;
}
FAT32_BPB *bpb2 = (FAT32_BPB *)buf;
mf_memset(bpb2, 0, 512);
mf_memcpy(bpb2->jump_boot, "\xEB\x58\x90", 3);
mf_memcpy(bpb2->oem_name, "MSDOS5.0", 8);
bpb2->bytes_per_sector = 512;
bpb2->sectors_per_cluster = spc;
bpb2->reserved_sector_count = (uint16_t)reserved_sectors;
bpb2->num_fats = num_fats;
bpb2->root_entry_count = 0;
bpb2->total_sectors_16 = 0;
bpb2->media = 0xF8;
bpb2->fat_size_16 = 0;
bpb2->sectors_per_track = 63;
bpb2->num_heads = 255;
bpb2->hidden_sectors = disk->partition_lba_offset;
bpb2->total_sectors_32 = sector_count;
bpb2->fat_size_32 = sectors_per_fat;
bpb2->ext_flags = 0;
bpb2->fs_version = 0;
bpb2->root_cluster = root_cluster;
bpb2->fs_info = 1;
bpb2->backup_boot_sector = 6;
bpb2->drive_number = 0x80;
bpb2->boot_sig = 0x29;
bpb2->volume_id = 0x12345678;
mf_memcpy(bpb2->volume_label, upper_label, 11);
mf_memcpy(bpb2->fs_type, "FAT32 ", 8);
bpb2->boot_signature = 0xAA55;
if (disk->write_sector(disk, 6, buf) != 0) {
serial_write("[MKFS] Error: failed to write backup BPB (sector 6)\n");
kfree(buf);
return -1;
}
FAT32_FSInfo *fsinfo2 = (FAT32_FSInfo *)buf;
mf_memset(fsinfo2, 0, 512);
fsinfo2->lead_sig = 0x41615252;
fsinfo2->struct_sig = 0x61417272;
fsinfo2->free_count = 0xFFFFFFFF;
fsinfo2->next_free = 0xFFFFFFFF;
fsinfo2->trail_sig = 0xAA550000;
if (disk->write_sector(disk, 7, buf) != 0) {
serial_write("[MKFS] Error: failed to write backup FSInfo (sector 7)\n");
kfree(buf);
return -1;
}
/* Zero both FATs */
mf_memset(buf, 0, 512);
for (uint32_t f = 0; f < num_fats; f++) {
uint32_t fat_start = reserved_sectors + (f * sectors_per_fat);
for (uint32_t s = 0; s < sectors_per_fat; s++) {
if (disk->write_sector(disk, fat_start + s, buf) != 0) {
serial_write("[MKFS] Error: failed to zero FAT\n");
kfree(buf);
return -1;
}
}
}
/* Write markers to both FATs */
mf_memset(buf, 0, 512);
uint32_t *fat_buf = (uint32_t *)buf;
fat_buf[0] = 0x0FFFFFF8; // Media type
fat_buf[1] = 0x0FFFFFFF; // Reserved
fat_buf[2] = 0x0FFFFFFF; // Root directory (Cluster 2)
for (uint32_t f = 0; f < num_fats; f++) {
uint32_t fat_start = reserved_sectors + (f * sectors_per_fat);
if (disk->write_sector(disk, fat_start, buf) != 0) {
serial_write("[MKFS] Error: failed to write FAT markers\n");
kfree(buf);
return -1;
}
}
/* Zero root cluster */
mf_memset(buf, 0, 512);
uint32_t root_start = reserved_sectors + num_fats * sectors_per_fat;
for (uint32_t s = 0; s < (uint32_t)spc; s++) {
if (disk->write_sector(disk, root_start + s, buf) != 0) {
serial_write("[MKFS] Error: failed to zero root cluster\n");
kfree(buf);
return -1;
}
}
kfree(buf);
disk->is_fat32 = true;
mf_set_disk_label(disk, upper_label);
serial_write("[MKFS] FAT32 formatted: ");
serial_write(disk->devname);
serial_write(" label=");
char lb[12];
mf_memcpy(lb, upper_label, 11);
lb[11] = 0;
for (int i = 10; i >= 0 && lb[i] == ' '; i--) lb[i] = 0;
serial_write(lb);
serial_write(" spc=");
serial_write_num(spc);
serial_write(" fat_sectors=");
serial_write_num(sectors_per_fat);
serial_write("\n");
return 0;
}

12
src/fs/mkfs_fat32.h Normal file
View file

@ -0,0 +1,12 @@
// 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 MKFS_FAT32_H
#define MKFS_FAT32_H
#include <stdint.h>
#include "../dev/disk.h"
#define MIN_FAT32_SECTORS 65536
int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label);
#endif

View file

@ -17,7 +17,7 @@ void* procfs_open(void *fs_private, const char *path, const char *mode) {
if (path[0] == '/') path++; if (path[0] == '/') path++;
procfs_handle_t *h = (procfs_handle_t*)kmalloc(sizeof(procfs_handle_t)); procfs_handle_t *h = (procfs_handle_t*)kmalloc(sizeof(procfs_handle_t));
k_memset(h, 0, sizeof(procfs_handle_t)); memset(h, 0, sizeof(procfs_handle_t));
h->offset = 0; h->offset = 0;
if (path[0] == '\0') { if (path[0] == '\0') {
@ -33,10 +33,10 @@ void* procfs_open(void *fs_private, const char *path, const char *mode) {
i++; i++;
} }
pid_str[i] = 0; pid_str[i] = 0;
h->pid = k_atoi(pid_str); h->pid = atoi(pid_str);
if (path[i] == '/') { if (path[i] == '/') {
k_strcpy(h->type, path + i + 1); strcpy(h->type, path + i + 1);
} else { } else {
h->type[0] = 0; h->type[0] = 0;
} }
@ -44,7 +44,7 @@ void* procfs_open(void *fs_private, const char *path, const char *mode) {
} }
h->pid = 0xFFFFFFFF; h->pid = 0xFFFFFFFF;
k_strcpy(h->type, path); strcpy(h->type, path);
return h; return h;
} }
@ -61,33 +61,33 @@ int procfs_read(void *fs_private, void *handle, void *buf, int size) {
out[0] = 0; out[0] = 0;
if (h->pid == 0xFFFFFFFF) { if (h->pid == 0xFFFFFFFF) {
if (k_strcmp(h->type, "version") == 0) { if (strcmp(h->type, "version") == 0) {
extern void get_os_info(os_info_t *info); extern void get_os_info(os_info_t *info);
os_info_t info; os_info_t info;
get_os_info(&info); get_os_info(&info);
k_strcpy(out, info.os_name); strcpy(out, info.os_name);
k_strcpy(out + k_strlen(out), " ["); strcpy(out + strlen(out), " [");
k_strcpy(out + k_strlen(out), info.os_codename); strcpy(out + strlen(out), info.os_codename);
k_strcpy(out + k_strlen(out), "] Version "); strcpy(out + strlen(out), "] Version ");
k_strcpy(out + k_strlen(out), info.os_version); strcpy(out + strlen(out), info.os_version);
k_strcpy(out + k_strlen(out), "\nKernel: "); strcpy(out + strlen(out), "\nKernel: ");
k_strcpy(out + k_strlen(out), info.kernel_name); strcpy(out + strlen(out), info.kernel_name);
k_strcpy(out + k_strlen(out), " "); strcpy(out + strlen(out), " ");
k_strcpy(out + k_strlen(out), info.kernel_version); strcpy(out + strlen(out), info.kernel_version);
k_strcpy(out + k_strlen(out), "\nBuild: "); strcpy(out + strlen(out), "\nBuild: ");
k_strcpy(out + k_strlen(out), info.build_date); strcpy(out + strlen(out), info.build_date);
k_strcpy(out + k_strlen(out), " "); strcpy(out + strlen(out), " ");
k_strcpy(out + k_strlen(out), info.build_time); strcpy(out + strlen(out), info.build_time);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} else if (k_strcmp(h->type, "uptime") == 0) { } else if (strcmp(h->type, "uptime") == 0) {
extern uint32_t wm_get_ticks(void); extern uint32_t wm_get_ticks(void);
uint32_t ticks = wm_get_ticks(); uint32_t ticks = wm_get_ticks();
k_itoa(ticks / 60, out); itoa(ticks / 60, out);
k_strcpy(out + k_strlen(out), " seconds\nRaw_Ticks:"); strcpy(out + strlen(out), " seconds\nRaw_Ticks:");
char t_s[16]; k_itoa(ticks, t_s); char t_s[16]; itoa(ticks, t_s);
k_strcpy(out + k_strlen(out), t_s); strcpy(out + strlen(out), t_s);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} else if (k_strcmp(h->type, "cpuinfo") == 0) { } else if (strcmp(h->type, "cpuinfo") == 0) {
extern uint32_t smp_cpu_count(void); extern uint32_t smp_cpu_count(void);
extern void platform_get_cpu_model(char *model); extern void platform_get_cpu_model(char *model);
extern void platform_get_cpu_vendor(char *vendor); extern void platform_get_cpu_vendor(char *vendor);
@ -111,35 +111,35 @@ int procfs_read(void *fs_private, void *handle, void *buf, int size) {
for (uint32_t i = 0; i < cpu_count; i++) { for (uint32_t i = 0; i < cpu_count; i++) {
char buf[32]; char buf[32];
k_strcpy(out + k_strlen(out), "processor\t: "); strcpy(out + strlen(out), "processor\t: ");
k_itoa(i, buf); itoa(i, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "vendor_id\t: "); strcpy(out + strlen(out), "vendor_id\t: ");
k_strcpy(out + k_strlen(out), vendor); strcpy(out + strlen(out), vendor);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "cpu family\t: "); strcpy(out + strlen(out), "cpu family\t: ");
k_itoa(info.family, buf); itoa(info.family, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "model\t\t: "); strcpy(out + strlen(out), "model\t\t: ");
k_itoa(info.model, buf); itoa(info.model, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "model name\t: "); strcpy(out + strlen(out), "model name\t: ");
k_strcpy(out + k_strlen(out), model); strcpy(out + strlen(out), model);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "stepping\t: "); strcpy(out + strlen(out), "stepping\t: ");
k_itoa(info.stepping, buf); itoa(info.stepping, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "microcode\t: 0x"); strcpy(out + strlen(out), "microcode\t: 0x");
char hex[16]; char hex[16];
int temp = info.microcode; int temp = info.microcode;
int hex_pos = 0; int hex_pos = 0;
@ -148,188 +148,217 @@ int procfs_read(void *fs_private, void *handle, void *buf, int size) {
hex[hex_pos++] = digit < 10 ? '0' + digit : 'a' + (digit - 10); hex[hex_pos++] = digit < 10 ? '0' + digit : 'a' + (digit - 10);
} }
hex[hex_pos] = '\0'; hex[hex_pos] = '\0';
k_strcpy(out + k_strlen(out), hex); strcpy(out + strlen(out), hex);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "cache size\t: "); strcpy(out + strlen(out), "cache size\t: ");
k_itoa(info.cache_size, buf); itoa(info.cache_size, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), " KB\n"); strcpy(out + strlen(out), " KB\n");
k_strcpy(out + k_strlen(out), "physical id\t: 0\n"); strcpy(out + strlen(out), "physical id\t: 0\n");
k_strcpy(out + k_strlen(out), "siblings\t: "); strcpy(out + strlen(out), "siblings\t: ");
k_itoa(cpu_count, buf); itoa(cpu_count, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "core id\t\t: "); strcpy(out + strlen(out), "core id\t\t: ");
k_itoa(i, buf); itoa(i, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "cpu cores\t: "); strcpy(out + strlen(out), "cpu cores\t: ");
k_itoa(cpu_count, buf); itoa(cpu_count, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "apicid\t\t: "); strcpy(out + strlen(out), "apicid\t\t: ");
k_itoa(i, buf); itoa(i, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "initial apicid\t: "); strcpy(out + strlen(out), "initial apicid\t: ");
k_itoa(i, buf); itoa(i, buf);
k_strcpy(out + k_strlen(out), buf); strcpy(out + strlen(out), buf);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "fpu\t\t: yes\n"); strcpy(out + strlen(out), "fpu\t\t: yes\n");
k_strcpy(out + k_strlen(out), "fpu_exception\t: yes\n"); strcpy(out + strlen(out), "fpu_exception\t: yes\n");
k_strcpy(out + k_strlen(out), "cpuid level\t: 13\n"); strcpy(out + strlen(out), "cpuid level\t: 13\n");
k_strcpy(out + k_strlen(out), "wp\t\t: yes\n"); strcpy(out + strlen(out), "wp\t\t: yes\n");
k_strcpy(out + k_strlen(out), "flags\t\t: "); strcpy(out + strlen(out), "flags\t\t: ");
k_strcpy(out + k_strlen(out), flags); strcpy(out + strlen(out), flags);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "bugs\t\t: \n"); strcpy(out + strlen(out), "bugs\t\t: \n");
k_strcpy(out + k_strlen(out), "bogomips\t: 4800.00\n"); strcpy(out + strlen(out), "bogomips\t: 4800.00\n");
if (i < cpu_count - 1) { if (i < cpu_count - 1) {
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} }
} }
} else if (k_strcmp(h->type, "meminfo") == 0) { } else if (strcmp(h->type, "datetime") == 0) {
extern void rtc_get_datetime(int *year, int *month, int *day, int *hour, int *minute, int *second);
int y, m, d, h_val, min, s;
rtc_get_datetime(&y, &m, &d, &h_val, &min, &s);
char buf[16];
itoa(y, buf);
strcpy(out, buf);
strcpy(out + strlen(out), "-");
if (m < 10) strcpy(out + strlen(out), "0");
itoa(m, buf);
strcpy(out + strlen(out), buf);
strcpy(out + strlen(out), "-");
if (d < 10) strcpy(out + strlen(out), "0");
itoa(d, buf);
strcpy(out + strlen(out), buf);
strcpy(out + strlen(out), " ");
if (h_val < 10) strcpy(out + strlen(out), "0");
itoa(h_val, buf);
strcpy(out + strlen(out), buf);
strcpy(out + strlen(out), ":");
if (min < 10) strcpy(out + strlen(out), "0");
itoa(min, buf);
strcpy(out + strlen(out), buf);
strcpy(out + strlen(out), ":");
if (s < 10) strcpy(out + strlen(out), "0");
itoa(s, buf);
strcpy(out + strlen(out), buf);
strcpy(out + strlen(out), "\n");
} else if (strcmp(h->type, "meminfo") == 0) {
extern MemStats memory_get_stats(void); extern MemStats memory_get_stats(void);
MemStats stats = memory_get_stats(); MemStats stats = memory_get_stats();
char m_s[32]; char m_s[32];
k_strcpy(out, "MemTotal:\t"); strcpy(out, "MemTotal:\t");
k_itoa(stats.total_memory / 1024, m_s); itoa(stats.total_memory / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "MemFree:\t"); strcpy(out + strlen(out), "MemFree:\t");
k_itoa(stats.available_memory / 1024, m_s); itoa(stats.available_memory / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "MemAvailable:\t"); strcpy(out + strlen(out), "MemAvailable:\t");
k_itoa(stats.available_memory / 1024, m_s); itoa(stats.available_memory / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "Buffers:\t0 kB\n"); strcpy(out + strlen(out), "Buffers:\t0 kB\n");
k_strcpy(out + k_strlen(out), "Cached:\t\t0 kB\n"); strcpy(out + strlen(out), "Cached:\t\t0 kB\n");
k_strcpy(out + k_strlen(out), "MemUsed:\t"); strcpy(out + strlen(out), "MemUsed:\t");
k_itoa(stats.used_memory / 1024, m_s); itoa(stats.used_memory / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "MemPeak:\t"); strcpy(out + strlen(out), "MemPeak:\t");
k_itoa(stats.peak_memory_used / 1024, m_s); itoa(stats.peak_memory_used / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "SwapTotal:\t0 kB\n"); strcpy(out + strlen(out), "SwapTotal:\t0 kB\n");
k_strcpy(out + k_strlen(out), "SwapFree:\t0 kB\n"); strcpy(out + strlen(out), "SwapFree:\t0 kB\n");
k_strcpy(out + k_strlen(out), "Dirty:\t\t0 kB\n"); strcpy(out + strlen(out), "Dirty:\t\t0 kB\n");
k_strcpy(out + k_strlen(out), "Writeback:\t0 kB\n"); strcpy(out + strlen(out), "Writeback:\t0 kB\n");
k_strcpy(out + k_strlen(out), "AnonPages:\t"); strcpy(out + strlen(out), "AnonPages:\t");
k_itoa(stats.used_memory / 1024, m_s); itoa(stats.used_memory / 1024, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), " kB\n"); strcpy(out + strlen(out), " kB\n");
k_strcpy(out + k_strlen(out), "Mapped:\t\t0 kB\n"); strcpy(out + strlen(out), "Mapped:\t\t0 kB\n");
k_strcpy(out + k_strlen(out), "Shmem:\t\t0 kB\n"); strcpy(out + strlen(out), "Shmem:\t\t0 kB\n");
k_strcpy(out + k_strlen(out), "Blocks:\t\t"); strcpy(out + strlen(out), "Blocks:\t\t");
k_itoa(stats.allocated_blocks, m_s); itoa(stats.allocated_blocks, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "FreeBlocks:\t"); strcpy(out + strlen(out), "FreeBlocks:\t");
k_itoa(stats.free_blocks, m_s); itoa(stats.free_blocks, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
k_strcpy(out + k_strlen(out), "Fragmentation:\t"); strcpy(out + strlen(out), "Fragmentation:\t");
k_itoa(stats.fragmentation_percent, m_s); itoa(stats.fragmentation_percent, m_s);
k_strcpy(out + k_strlen(out), m_s); strcpy(out + strlen(out), m_s);
k_strcpy(out + k_strlen(out), "%\n"); strcpy(out + strlen(out), "%\n");
} else if (k_strcmp(h->type, "devices") == 0) { } else if (strcmp(h->type, "devices") == 0) {
extern int disk_get_count(void); extern int disk_get_count(void);
extern Disk* disk_get_by_index(int index); extern Disk* disk_get_by_index(int index);
int dcount = disk_get_count(); int dcount = disk_get_count();
out[0] = '\0'; out[0] = '\0';
k_strcpy(out, "Character devices:\n"); strcpy(out, "Character devices:\n");
k_strcpy(out + k_strlen(out), " 1 mem\n"); strcpy(out + strlen(out), " 1 mem\n");
k_strcpy(out + k_strlen(out), " 4 tty\n"); strcpy(out + strlen(out), " 4 tty\n");
k_strcpy(out + k_strlen(out), " 5 cua\n"); strcpy(out + strlen(out), " 5 cua\n");
k_strcpy(out + k_strlen(out), " 7 vcs\n"); strcpy(out + strlen(out), " 7 vcs\n");
k_strcpy(out + k_strlen(out), " 8 stdin\n"); strcpy(out + strlen(out), " 8 stdin\n");
k_strcpy(out + k_strlen(out), " 13 input\n"); strcpy(out + strlen(out), " 13 input\n");
k_strcpy(out + k_strlen(out), " 14 sound\n"); strcpy(out + strlen(out), " 14 sound\n");
k_strcpy(out + k_strlen(out), " 29 fb\n"); strcpy(out + strlen(out), " 29 fb\n");
k_strcpy(out + k_strlen(out), "189 usb\n\n"); strcpy(out + strlen(out), "189 usb\n\n");
k_strcpy(out + k_strlen(out), "Block devices:\n"); strcpy(out + strlen(out), "Block devices:\n");
for (int i = 0; i < dcount; i++) { for (int i = 0; i < dcount; i++) {
Disk *d = disk_get_by_index(i); Disk *d = disk_get_by_index(i);
if (d && !d->is_partition) { if (d && !d->is_partition) {
k_strcpy(out + k_strlen(out), " 8 "); strcpy(out + strlen(out), " 8 ");
k_strcpy(out + k_strlen(out), d->devname); strcpy(out + strlen(out), d->devname);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} }
} }
k_strcpy(out + k_strlen(out), " 11 sr\n"); strcpy(out + strlen(out), " 11 sr\n");
k_strcpy(out + k_strlen(out), "253 virtblk\n"); strcpy(out + strlen(out), "253 virtblk\n");
} }
} }
else { else {
process_t *proc = process_get_by_pid(h->pid); process_t *proc = process_get_by_pid(h->pid);
if (!proc) { kfree(out); return -1; } if (!proc) { kfree(out); return -1; }
if (k_strcmp(h->type, "name") == 0 || k_strcmp(h->type, "cmdline") == 0) { if (strcmp(h->type, "name") == 0 || strcmp(h->type, "cmdline") == 0) {
k_strcpy(out, proc->name); strcpy(out, proc->name);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} else if (k_strcmp(h->type, "cwd") == 0) { } else if (strcmp(h->type, "cwd") == 0) {
k_strcpy(out, proc->cwd); strcpy(out, proc->cwd);
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} else if (k_strcmp(h->type, "status") == 0) { } else if (strcmp(h->type, "status") == 0) {
k_strcpy(out, "Name: "); strcpy(out, "Name: ");
k_strcpy(out + k_strlen(out), proc->name); strcpy(out + strlen(out), proc->name);
k_strcpy(out + k_strlen(out), "\nPID: "); strcpy(out + strlen(out), "\nPID: ");
char pid_s[16]; k_itoa(proc->pid, pid_s); char pid_s[16]; itoa(proc->pid, pid_s);
k_strcpy(out + k_strlen(out), pid_s); strcpy(out + strlen(out), pid_s);
k_strcpy(out + k_strlen(out), "\nState: RUNNING\nMemory: "); strcpy(out + strlen(out), "\nState: RUNNING\nMemory: ");
uint64_t mem_val = proc->used_memory; uint64_t mem_val = proc->used_memory;
if (h->pid == 0) { if (h->pid == 0) {
extern MemStats memory_get_stats(void); extern MemStats memory_get_stats(void);
mem_val = memory_get_stats().used_memory; mem_val = memory_get_stats().used_memory;
} }
char mem_s[32]; k_itoa(mem_val / 1024, mem_s); char mem_s[32]; itoa(mem_val / 1024, mem_s);
k_strcpy(out + k_strlen(out), mem_s); strcpy(out + strlen(out), mem_s);
k_strcpy(out + k_strlen(out), " KB\nTicks: "); strcpy(out + strlen(out), " KB\nTicks: ");
char tick_s[32]; k_itoa(proc->ticks, tick_s); char tick_s[32]; itoa(proc->ticks, tick_s);
k_strcpy(out + k_strlen(out), tick_s); strcpy(out + strlen(out), tick_s);
k_strcpy(out + k_strlen(out), "\nIdle: "); strcpy(out + strlen(out), "\nIdle: ");
k_strcpy(out + k_strlen(out), proc->is_idle ? "1" : "0"); strcpy(out + strlen(out), proc->is_idle ? "1" : "0");
k_strcpy(out + k_strlen(out), "\n"); strcpy(out + strlen(out), "\n");
} }
} }
int len = k_strlen(out); int len = strlen(out);
if (h->offset >= len) { kfree(out); return 0; } if (h->offset >= len) { kfree(out); return 0; }
int to_copy = len - h->offset; int to_copy = len - h->offset;
if (to_copy > size) to_copy = size; if (to_copy > size) to_copy = size;
k_memcpy(buf, out + h->offset, to_copy); memcpy(buf, out + h->offset, to_copy);
h->offset += to_copy; h->offset += to_copy;
kfree(out); kfree(out);
return to_copy; return to_copy;
@ -339,13 +368,13 @@ int procfs_write(void *fs_private, void *handle, const void *buf, int size) {
procfs_handle_t *h = (procfs_handle_t*)handle; procfs_handle_t *h = (procfs_handle_t*)handle;
if (!h || h->pid == 0xFFFFFFFF) return -1; if (!h || h->pid == 0xFFFFFFFF) return -1;
if (k_strcmp(h->type, "signal") == 0) { if (strcmp(h->type, "signal") == 0) {
char cmd[16]; char cmd[16];
int to_copy = size < 15 ? size : 15; int to_copy = size < 15 ? size : 15;
k_memcpy(cmd, buf, to_copy); memcpy(cmd, buf, to_copy);
cmd[to_copy] = 0; cmd[to_copy] = 0;
if (k_strcmp(cmd, "9") == 0 || k_strcmp(cmd, "kill") == 0) { if (strcmp(cmd, "9") == 0 || strcmp(cmd, "kill") == 0) {
process_t *proc = process_get_by_pid(h->pid); process_t *proc = process_get_by_pid(h->pid);
if (proc && proc->pid != 0) { if (proc && proc->pid != 0) {
process_terminate(proc); process_terminate(proc);
@ -362,21 +391,23 @@ int procfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, in
if (path[0] == '\0') { if (path[0] == '\0') {
int out = 0; int out = 0;
k_strcpy(entries[out++].name, "version"); strcpy(entries[out++].name, "version");
entries[out-1].is_directory = 0; entries[out-1].is_directory = 0;
k_strcpy(entries[out++].name, "uptime"); strcpy(entries[out++].name, "uptime");
entries[out-1].is_directory = 0; entries[out-1].is_directory = 0;
k_strcpy(entries[out++].name, "cpuinfo"); strcpy(entries[out++].name, "cpuinfo");
entries[out-1].is_directory = 0; entries[out-1].is_directory = 0;
k_strcpy(entries[out++].name, "meminfo"); strcpy(entries[out++].name, "meminfo");
entries[out-1].is_directory = 0; entries[out-1].is_directory = 0;
k_strcpy(entries[out++].name, "devices"); strcpy(entries[out++].name, "datetime");
entries[out-1].is_directory = 0;
strcpy(entries[out++].name, "devices");
entries[out-1].is_directory = 0; entries[out-1].is_directory = 0;
extern process_t processes[]; extern process_t processes[];
for (int i = 0; i < 16 && out < max; i++) { for (int i = 0; i < 16 && out < max; i++) {
if (processes[i].pid != 0xFFFFFFFF) { if (processes[i].pid != 0xFFFFFFFF) {
k_itoa(processes[i].pid, entries[out].name); itoa(processes[i].pid, entries[out].name);
entries[out].is_directory = 1; entries[out].is_directory = 1;
entries[out].size = 0; entries[out].size = 0;
out++; out++;
@ -387,11 +418,11 @@ int procfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, in
if (path[0] >= '0' && path[0] <= '9') { if (path[0] >= '0' && path[0] <= '9') {
int out = 0; int out = 0;
k_strcpy(entries[out++].name, "name"); strcpy(entries[out++].name, "name");
k_strcpy(entries[out++].name, "status"); strcpy(entries[out++].name, "status");
k_strcpy(entries[out++].name, "cmdline"); strcpy(entries[out++].name, "cmdline");
k_strcpy(entries[out++].name, "cwd"); strcpy(entries[out++].name, "cwd");
k_strcpy(entries[out++].name, "signal"); strcpy(entries[out++].name, "signal");
for(int i=0; i<out; i++) entries[i].is_directory = 0; for(int i=0; i<out; i++) entries[i].is_directory = 0;
return out; return out;
} }
@ -411,13 +442,13 @@ bool procfs_exists(void *fs_private, const char *path) {
i++; i++;
} }
pid_str[i] = 0; pid_str[i] = 0;
uint32_t pid = k_atoi(pid_str); uint32_t pid = atoi(pid_str);
if (process_get_by_pid(pid)) return true; if (process_get_by_pid(pid)) return true;
} }
if (k_strcmp(path, "version") == 0 || k_strcmp(path, "uptime") == 0) return true; if (strcmp(path, "version") == 0 || strcmp(path, "uptime") == 0) return true;
if (k_strcmp(path, "cpuinfo") == 0 || k_strcmp(path, "meminfo") == 0) return true; if (strcmp(path, "cpuinfo") == 0 || strcmp(path, "meminfo") == 0) return true;
if (k_strcmp(path, "devices") == 0) return true; if (strcmp(path, "datetime") == 0 || strcmp(path, "devices") == 0) return true;
return false; return false;
} }
@ -436,6 +467,14 @@ bool procfs_is_dir(void *fs_private, const char *path) {
return false; return false;
} }
static int procfs_statfs(void *fs_private, vfs_statfs_t *stat) {
(void)fs_private;
stat->total_blocks = 0;
stat->free_blocks = 0;
stat->block_size = 512;
return 0;
}
vfs_fs_ops_t procfs_ops = { vfs_fs_ops_t procfs_ops = {
.open = procfs_open, .open = procfs_open,
.close = procfs_close, .close = procfs_close,
@ -443,7 +482,8 @@ vfs_fs_ops_t procfs_ops = {
.write = procfs_write, .write = procfs_write,
.readdir = procfs_readdir, .readdir = procfs_readdir,
.exists = procfs_exists, .exists = procfs_exists,
.is_dir = procfs_is_dir .is_dir = procfs_is_dir,
.statfs = procfs_statfs
}; };
vfs_fs_ops_t* procfs_get_ops(void) { vfs_fs_ops_t* procfs_get_ops(void) {

View file

@ -19,14 +19,14 @@ static void* sysfs_open(void *fs_private, const char *path, const char *mode) {
if (last_slash != -1) { if (last_slash != -1) {
char prefix[64]; char prefix[64];
k_memcpy(prefix, path, last_slash); memcpy(prefix, path, last_slash);
prefix[last_slash] = 0; prefix[last_slash] = 0;
sub = subsystem_get_by_name(prefix); sub = subsystem_get_by_name(prefix);
if (sub) { if (sub) {
const char *filename = path + last_slash + 1; const char *filename = path + last_slash + 1;
for (int j = 0; j < sub->file_count; j++) { for (int j = 0; j < sub->file_count; j++) {
if (k_strcmp(sub->files[j].name, filename) == 0) { if (strcmp(sub->files[j].name, filename) == 0) {
sysfs_handle_t *h = (sysfs_handle_t*)kmalloc(sizeof(sysfs_handle_t)); sysfs_handle_t *h = (sysfs_handle_t*)kmalloc(sizeof(sysfs_handle_t));
h->sub = sub; h->sub = sub;
h->file = &sub->files[j]; h->file = &sub->files[j];
@ -70,7 +70,7 @@ static int sysfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entri
if (exact_sub) { if (exact_sub) {
for (int i = 0; i < exact_sub->file_count && out < max; i++) { for (int i = 0; i < exact_sub->file_count && out < max; i++) {
k_strcpy(entries[out].name, exact_sub->files[i].name); strcpy(entries[out].name, exact_sub->files[i].name);
entries[out].is_directory = 0; entries[out].is_directory = 0;
entries[out].size = 0; entries[out].size = 0;
out++; out++;
@ -78,11 +78,11 @@ static int sysfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entri
} }
int count = subsystem_get_count(); int count = subsystem_get_count();
int path_len = k_strlen(path); int path_len = strlen(path);
for (int i = 0; i < count && out < max; i++) { for (int i = 0; i < count && out < max; i++) {
kernel_subsystem_t *s = subsystem_get_by_index(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] == '/')) { if (path_len == 0 || (strlen(s->name) > path_len && strncmp(s->name, path, path_len) == 0 && s->name[path_len] == '/')) {
const char *sub_path = s->name + (path_len ? path_len + 1 : 0); const char *sub_path = s->name + (path_len ? path_len + 1 : 0);
char comp[64]; char comp[64];
int j = 0; int j = 0;
@ -96,13 +96,13 @@ static int sysfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entri
bool found = false; bool found = false;
for (int k = 0; k < out; k++) { for (int k = 0; k < out; k++) {
if (k_strcmp(entries[k].name, comp) == 0) { if (strcmp(entries[k].name, comp) == 0) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
k_strcpy(entries[out].name, comp); strcpy(entries[out].name, comp);
entries[out].is_directory = 1; entries[out].is_directory = 1;
entries[out].size = 0; entries[out].size = 0;
out++; out++;
@ -123,22 +123,22 @@ static bool sysfs_exists(void *fs_private, const char *path) {
for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j; for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j;
if (last_slash != -1) { if (last_slash != -1) {
char prefix[64]; char prefix[64];
k_memcpy(prefix, path, last_slash); memcpy(prefix, path, last_slash);
prefix[last_slash] = 0; prefix[last_slash] = 0;
kernel_subsystem_t *sub = subsystem_get_by_name(prefix); kernel_subsystem_t *sub = subsystem_get_by_name(prefix);
if (sub) { if (sub) {
const char *filename = path + last_slash + 1; const char *filename = path + last_slash + 1;
for (int j = 0; j < sub->file_count; j++) { for (int j = 0; j < sub->file_count; j++) {
if (k_strcmp(sub->files[j].name, filename) == 0) return true; if (strcmp(sub->files[j].name, filename) == 0) return true;
} }
} }
} }
int count = subsystem_get_count(); int count = subsystem_get_count();
int path_len = k_strlen(path); int path_len = strlen(path);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
kernel_subsystem_t *s = subsystem_get_by_index(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; if (strlen(s->name) > path_len && strncmp(s->name, path, path_len) == 0 && s->name[path_len] == '/') return true;
} }
return false; return false;
@ -152,13 +152,13 @@ static bool sysfs_is_dir(void *fs_private, const char *path) {
for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j; for (int j = 0; path[j]; j++) if (path[j] == '/') last_slash = j;
if (last_slash != -1) { if (last_slash != -1) {
char prefix[64]; char prefix[64];
k_memcpy(prefix, path, last_slash); memcpy(prefix, path, last_slash);
prefix[last_slash] = 0; prefix[last_slash] = 0;
kernel_subsystem_t *sub = subsystem_get_by_name(prefix); kernel_subsystem_t *sub = subsystem_get_by_name(prefix);
if (sub) { if (sub) {
const char *filename = path + last_slash + 1; const char *filename = path + last_slash + 1;
for (int j = 0; j < sub->file_count; j++) { for (int j = 0; j < sub->file_count; j++) {
if (k_strcmp(sub->files[j].name, filename) == 0) return false; if (strcmp(sub->files[j].name, filename) == 0) return false;
} }
} }
} }
@ -166,6 +166,14 @@ static bool sysfs_is_dir(void *fs_private, const char *path) {
return sysfs_exists(fs_private, path); return sysfs_exists(fs_private, path);
} }
static int sysfs_statfs(void *fs_private, vfs_statfs_t *stat) {
(void)fs_private;
stat->total_blocks = 0;
stat->free_blocks = 0;
stat->block_size = 512;
return 0;
}
vfs_fs_ops_t sysfs_ops = { vfs_fs_ops_t sysfs_ops = {
.open = sysfs_open, .open = sysfs_open,
.close = sysfs_close, .close = sysfs_close,
@ -173,7 +181,8 @@ vfs_fs_ops_t sysfs_ops = {
.write = sysfs_write, .write = sysfs_write,
.readdir = sysfs_readdir, .readdir = sysfs_readdir,
.exists = sysfs_exists, .exists = sysfs_exists,
.is_dir = sysfs_is_dir .is_dir = sysfs_is_dir,
.statfs = sysfs_statfs
}; };
vfs_fs_ops_t* sysfs_get_ops(void) { vfs_fs_ops_t* sysfs_get_ops(void) {

View file

@ -3,6 +3,7 @@
// This header needs to maintain in any file it is present in, as per the GPL license terms. // This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "tar.h" #include "tar.h"
#include "fat32.h" #include "fat32.h"
#include "bootfs.h"
// The standard TAR header block is 512 bytes. // The standard TAR header block is 512 bytes.
struct tar_header { struct tar_header {
@ -112,7 +113,11 @@ void tar_parse(void *archive, uint64_t archive_size) {
tar_mkdir_recursive(parent_path); tar_mkdir_recursive(parent_path);
} }
// Extract the file data block directly into the VFS if (full_path[0] == '/' && full_path[1] == 'b' && full_path[2] == 'o' &&
full_path[3] == 'o' && full_path[4] == 't' && full_path[5] == '/') {
bootfs_register_file(full_path + 6, ptr + 512, (uint32_t)file_size);
}
FAT32_FileHandle *fh = fat32_open(full_path, "w"); FAT32_FileHandle *fh = fat32_open(full_path, "w");
if (fh && fh->valid) { if (fh && fh->valid) {
fat32_write(fh, ptr + 512, file_size); fat32_write(fh, ptr + 512, file_size);

View file

@ -710,6 +710,26 @@ bool vfs_is_directory(const char *path) {
return mount->ops->is_dir(mount->fs_private, rel_path); return mount->ops->is_dir(mount->fs_private, rel_path);
} }
int vfs_statfs(const char *path, vfs_statfs_t *stat) {
if (!path || !stat) 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);
if (!mount) return -1;
if (mount->ops->statfs) {
return mount->ops->statfs(mount->fs_private, stat);
}
stat->total_blocks = 0;
stat->free_blocks = 0;
stat->block_size = 512;
return 0;
}
int vfs_get_info(const char *path, vfs_dirent_t *info) { int vfs_get_info(const char *path, vfs_dirent_t *info) {
if (!path || !info) return -1; if (!path || !info) return -1;

View file

@ -13,6 +13,13 @@
#define VFS_MAX_MOUNTS 16 #define VFS_MAX_MOUNTS 16
#define VFS_MAX_OPEN_FILES 64 #define VFS_MAX_OPEN_FILES 64
// statfs structure
typedef struct {
uint64_t total_blocks;
uint64_t free_blocks;
uint64_t block_size;
} vfs_statfs_t;
// Forward declarations // Forward declarations
typedef struct vfs_mount vfs_mount_t; typedef struct vfs_mount vfs_mount_t;
typedef struct vfs_file vfs_file_t; typedef struct vfs_file vfs_file_t;
@ -47,6 +54,7 @@ typedef struct vfs_fs_ops {
bool (*exists)(void *fs_private, const char *rel_path); bool (*exists)(void *fs_private, const char *rel_path);
bool (*is_dir)(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); int (*get_info)(void *fs_private, const char *rel_path, vfs_dirent_t *info);
int (*statfs)(void *fs_private, vfs_statfs_t *stat);
// Handle info (for backward compat with syscall position/size queries) // Handle info (for backward compat with syscall position/size queries)
uint32_t (*get_position)(void *file_handle); uint32_t (*get_position)(void *file_handle);
@ -99,6 +107,7 @@ bool vfs_rename(const char *old_path, const char *new_path);
bool vfs_exists(const char *path); bool vfs_exists(const char *path);
bool vfs_is_directory(const char *path); bool vfs_is_directory(const char *path);
int vfs_get_info(const char *path, vfs_dirent_t *info); int vfs_get_info(const char *path, vfs_dirent_t *info);
int vfs_statfs(const char *path, vfs_statfs_t *stat);
// Mount enumeration // Mount enumeration
int vfs_get_mount_count(void); int vfs_get_mount_count(void);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 KiB

Some files were not shown because too many files have changed in this diff Show more