P0..P71 baseline before fbcond/fbbootlogd init fix

This commit is contained in:
2026-05-20 00:30:18 +03:00
parent 861e6f88d2
commit 9233726f58
19 changed files with 41854 additions and 57 deletions
+31 -3
View File
@@ -85,6 +85,8 @@ Layer 2: Durable (survives clean/fetch/rebuild/release provisioning)
|---|---| |---|---|
| Editing `source/` files then running `make all` | `make all` calls `repo fetch` which regenerates `source/` — edits are lost | | Editing `source/` files then running `make all` | `make all` calls `repo fetch` which regenerates `source/` — edits are lost |
| Creating a patch but not wiring it into `recipe.toml` | Patch file exists but is never applied — build uses unpatched source | | Creating a patch but not wiring it into `recipe.toml` | Patch file exists but is never applied — build uses unpatched source |
| **Hand-writing patches manually** | **FORBIDDEN. Unified diffs hand-written by humans routinely have incorrect line counts, wrong context, malformed hunks, or timestamp headers — all of which cause `patch(1)` to reject them. The ONLY acceptable way to generate patches is `git diff -U0 -w` from a committed source tree baseline.** |
| Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error |
| Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error | | Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error |
| Expecting `source/` changes to survive `make clean` | `make clean` deletes `source/` directories | | Expecting `source/` changes to survive `make clean` | `make clean` deletes `source/` directories |
| Running `repo cook` without `--allow-protected` for core packages | Protected recipes (kernel, relibc, base) are offline-only by default | | Running `repo cook` without `--allow-protected` for core packages | Protected recipes (kernel, relibc, base) are offline-only by default |
@@ -649,19 +651,39 @@ does NOT strip timestamps — they should be removed from the patch file directl
### Robust Patch Generation (REQUIRED) ### Robust Patch Generation (REQUIRED)
**MANDATORY: All patches MUST be generated using `git diff -U0 -w` from a committed source tree.
Hand-writing unified diffs is FORBIDDEN — it routinely produces incorrect line counts, malformed
hunks, or timestamp headers that cause `patch(1)` to reject them. The build system uses
`--fuzz=3` for resilient context matching, which requires properly generated diffs.**
Context-line mismatches (renamed variables, shifted line numbers, upstream refactors) Context-line mismatches (renamed variables, shifted line numbers, upstream refactors)
are the single largest source of patch application failures. Use the zero-context, are the single largest source of patch application failures. Use the zero-context,
whitespace-ignored technique to make patches resilient to drift: whitespace-ignored technique to make patches resilient to drift:
**Generate:** **Workflow (mandatory):**
```bash ```bash
# 1. Start with a clean P0..P(N-1) source tree (repo fetch already applied earlier patches)
cd recipes/<component>/source cd recipes/<component>/source
# 2. Commit the P0..P(N-1) state as a git baseline
git add -A && git commit -m "P0..P(N-1) baseline"
# 3. Make P(N) edits in the source tree
# (edit files, test compile, etc.)
# 4. Generate the P(N) patch using ONLY git diff -U0 -w:
git diff -U0 -w > ../../../local/patches/<component>/P<N>-<description>.patch git diff -U0 -w > ../../../local/patches/<component>/P<N>-<description>.patch
# 5. Wire the patch into recipe.toml patches list
# 6. Validate: repo validate-patches <package>
# 7. Rebuild: repo cook <package>
# 8. Commit: git add local/patches/ recipes/<pkg>/recipe.toml && git commit
``` ```
**Apply:** **Apply (for manual testing):**
```bash ```bash
patch -p1 --fuzz=3 < local/patches/<component>/P<N>-<description>.patch patch -p1 --fuzz=3 < local/patches/<component>/P<N>-<description>.patch>
``` ```
**Why this works:** **Why this works:**
@@ -671,6 +693,12 @@ patch -p1 --fuzz=3 < local/patches/<component>/P<N>-<description>.patch
- `--fuzz=3` allows `patch(1)` to find the target location even when nearby lines have shifted - `--fuzz=3` allows `patch(1)` to find the target location even when nearby lines have shifted
- Together these three flags eliminate the entire class of "context mismatch" failures - Together these three flags eliminate the entire class of "context mismatch" failures
**Why hand-writing is forbidden:**
- Human-written diffs routinely have wrong `@@` line counts, missing or extra context lines,
incorrect `--- a/` / `+++ b/` paths, or embedded timestamps — all of which cause `patch(1)`
to reject the patch or silently apply it to the wrong location
- The `git diff -U0 -w` command produces mechanically correct diffs every time
**Before this technique**, patches routinely broke when: **Before this technique**, patches routinely broke when:
- A variable was renamed (e.g., `deamon``daemon` in context) - A variable was renamed (e.g., `deamon``daemon` in context)
- Lines were added or removed above the changed code - Lines were added or removed above the changed code
+1 -1
View File
@@ -18,7 +18,7 @@ path = "/usr/lib/init.d/10_acid.service"
data = """ data = """
[unit] [unit]
description = "Acid test runner" description = "Acid test runner"
requires_weak = ["00_pcid-spawner.service"] requires_weak = ["00_driver-manager.service"]
[service] [service]
cmd = "ion" cmd = "ion"
+2 -2
View File
@@ -380,8 +380,8 @@ vendor = 0xFFFF
device = 0xFFFF device = 0xFFFF
""" """
# Profiles that include this fragment should start `driver-manager` instead of # driver-manager owns PCI device enumeration, driver matching, and bind/channel
# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself. # handoff — replacing the old pcid + pcid-spawner pair entirely.
[[files]] [[files]]
path = "/etc/init.d/00_driver-manager.service" path = "/etc/init.d/00_driver-manager.service"
data = """ data = """
+4 -1
View File
@@ -447,8 +447,9 @@ path = "/etc/init.d/29_activate_console.service"
data = """ data = """
[unit] [unit]
description = "Activate fallback console VT" description = "Activate fallback console VT"
default_dependencies = false
requires_weak = [ requires_weak = [
"08_userland.target", "00_base.target",
] ]
[service] [service]
@@ -462,6 +463,7 @@ path = "/etc/init.d/30_console.service"
data = """ data = """
[unit] [unit]
description = "Console terminals" description = "Console terminals"
default_dependencies = false
requires_weak = [ requires_weak = [
"29_activate_console.service", "29_activate_console.service",
] ]
@@ -477,6 +479,7 @@ path = "/etc/init.d/31_debug_console.service"
data = """ data = """
[unit] [unit]
description = "Debug console on serial port" description = "Debug console on serial port"
default_dependencies = false
requires_weak = [ requires_weak = [
"29_activate_console.service", "29_activate_console.service",
] ]
+4 -22
View File
@@ -3,14 +3,9 @@
# 00_base.service: stripped base setup (tmpdir only, no sudo — sudo runs from # 00_base.service: stripped base setup (tmpdir only, no sudo — sudo runs from
# base.toml's 00_sudo.service). ipcd and ptyd are started by # base.toml's 00_sudo.service). ipcd and ptyd are started by
# 00_ipcd.service and 00_ptyd.service from the base recipe. # 00_ipcd.service and 00_ptyd.service from the base recipe.
# 00_drivers / 10_net: no longer overridden — the legacy scripts were removed # 00_pcid-spawner.service has been fully replaced by 00_driver-manager.service
# from base.toml. The retained 00_pcid-spawner.service unit name now # (defined in redbear-device-services.toml). The old pcid-spawner
# launches driver-manager so existing init ordering remains stable. # unit name is no longer used anywhere.
# 00_pcid-spawner.service: compatibility wrapper for driver-manager. The base
# recipe uses type="oneshot" which blocks init until pcid-spawner exits.
# Running driver-manager here with oneshot_async keeps the historic unit
# name for downstream `requires_weak` consumers while moving PCI driver
# spawning to the manager that performs bind/channel handoff.
[packages] [packages]
zsh = {} zsh = {}
@@ -37,17 +32,4 @@ default_dependencies = false
[service] [service]
cmd = "audiod" cmd = "audiod"
type = "oneshot_async" type = "oneshot_async"
""" """
[[files]]
path = "/etc/init.d/00_pcid-spawner.service"
data = """
[unit]
description = "PCI driver spawner compatibility alias"
default_dependencies = false
[service]
cmd = "echo"
args = ["pcid-spawner compatibility alias: driver-manager owns PCI driver spawning"]
type = "oneshot"
"""
+19 -20
View File
@@ -27,9 +27,8 @@ redbear-release = {}
redbear-hwutils = {} redbear-hwutils = {}
redbear-quirks = {} redbear-quirks = {}
# Device driver infrastructure: driver-manager is started by # Device driver infrastructure: driver-manager replaces pcid-spawner;
# redbear-device-services.toml, with 00_pcid-spawner.service retained only as a # 00_driver-manager.service is defined in redbear-device-services.toml.
# compatibility dependency alias for older service units.
ehcid = {} ehcid = {}
ohcid = {} ohcid = {}
uhcid = {} uhcid = {}
@@ -99,7 +98,7 @@ meson = {}
ninja-build = {} ninja-build = {}
m4 = {} m4 = {}
#git = {} # suppressed: cascading rebuild; git not needed for boot/recovery #git = {} # suppressed: cascading rebuild; git not needed for boot/recovery
htop = {} #htop = {} # disabled: build failure in redoxer env (pre-existing)
#mc = {} # suppressed: C99 format warning errors in compilation #mc = {} # suppressed: C99 format warning errors in compilation
# ── Build / packaging utilities ── # ── Build / packaging utilities ──
@@ -473,23 +472,7 @@ data = ""
directory = true directory = true
mode = 0o755 mode = 0o755
[[files]]
path = "/etc/pcid.d/ihdgd.toml"
data = """
# redbear-live-mini: text-only image; override upstream ihdgd config with empty file
"""
[[files]]
path = "/etc/pcid.d/virtio-gpud.toml"
data = """
# redbear-live-mini: text-only image; override upstream virtio-gpud config with empty file
"""
[[files]]
path = "/etc/pcid.d/00_text_mode_gpu_mask.toml"
data = """
# redbear-live-mini: no display driver matched; class 0x03 devices are skipped
"""
[[files]] [[files]]
path = "/lib/drivers.d/30-graphics.toml" path = "/lib/drivers.d/30-graphics.toml"
@@ -508,6 +491,7 @@ path = "/etc/init.d/29_activate_console.service"
data = """ data = """
[unit] [unit]
description = "Activate console VT" description = "Activate console VT"
default_dependencies = false
requires_weak = ["00_base.target"] requires_weak = ["00_base.target"]
[service] [service]
@@ -521,6 +505,7 @@ path = "/etc/init.d/30_console.service"
data = """ data = """
[unit] [unit]
description = "Console terminals" description = "Console terminals"
default_dependencies = false
requires_weak = ["29_activate_console.service"] requires_weak = ["29_activate_console.service"]
[service] [service]
@@ -534,6 +519,7 @@ path = "/etc/init.d/31_debug_console.service"
data = """ data = """
[unit] [unit]
description = "Debug console" description = "Debug console"
default_dependencies = false
requires_weak = ["29_activate_console.service"] requires_weak = ["29_activate_console.service"]
[service] [service]
@@ -541,3 +527,16 @@ cmd = "getty"
args = ["/scheme/debug/no-preserve", "-J"] args = ["/scheme/debug/no-preserve", "-J"]
type = "oneshot_async" type = "oneshot_async"
""" """
[[files]]
path = "/etc/init.d/08_userland.target"
data = """
[unit]
description = "Userland services target"
requires_weak = [
"06_services.target",
"29_activate_console.service",
"30_console.service",
"31_debug_console.service",
]
"""
+1 -1
View File
@@ -21,7 +21,7 @@ path = "/usr/lib/init.d/10_smolnetd.service"
data = """ data = """
[unit] [unit]
description = "Network stack for redoxer" description = "Network stack for redoxer"
requires_weak = ["00_pcid-spawner.service"] requires_weak = ["00_driver-manager.service"]
[service] [service]
cmd = "netstack" cmd = "netstack"
@@ -5,3 +5,30 @@ index bb512c60..3c3ed97d 100644
@@ -4 +4 @@ default_dependencies = false @@ -4 +4 @@ default_dependencies = false
-requires_weak = ["00_randd.service"] -requires_weak = ["00_randd.service"]
+requires = ["00_randd.service"] +requires = ["00_randd.service"]
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
index 1bfc5c48..8db29281 100644
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -120,2 +120,20 @@ if !hard_deps_met {
- init_warn(&format!("{}: hard dependency not met, skipping", job.unit.0));
- self.completed.insert(job.unit);
+ let unit = unit_store.unit(&job.unit);
+ let unit_id_str = job.unit.0.clone();
+ let all_deps_missing = unit.info.requires.iter().all(|dep| {
+ self.completed.contains(dep) || !unit_store.has_unit(dep)
+ });
+ if all_deps_missing {
+ init_warn(&format!("{}: hard dependency not met, skipping", unit_id_str));
+ self.completed.insert(job.unit);
+ continue 'a;
+ }
+ defer_count += 1;
+ self.pending.push_back(job);
+ if defer_count > self.pending.len() + self.completed.len() {
+ init_warn(&format!(
+ "{}: hard dependency not met after deferring, skipping",
+ unit_id_str
+ ));
+ self.completed.insert(UnitId(unit_id_str));
+ return;
+ }
@@ -0,0 +1,203 @@
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
index a4c07d1e..4db8c738 100644
--- a/drivers/graphics/vesad/src/main.rs
+++ b/drivers/graphics/vesad/src/main.rs
@@ -25,20 +25,60 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let width = usize::from_str_radix(
- &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"),
- 16,
- )
- .expect("failed to parse FRAMEBUFFER_WIDTH");
- let height = usize::from_str_radix(
- &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"),
- 16,
- )
- .expect("failed to parse FRAMEBUFFER_HEIGHT");
- let phys = usize::from_str_radix(
- &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"),
- 16,
- )
- .expect("failed to parse FRAMEBUFFER_ADDR");
- let stride = usize::from_str_radix(
- &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"),
- 16,
- )
- .expect("failed to parse FRAMEBUFFER_STRIDE");
+ let width = match env::var("FRAMEBUFFER_WIDTH") {
+ Ok(v) => match usize::from_str_radix(&v, 16) {
+ Ok(n) => n,
+ Err(e) => {
+ eprintln!("vesad: failed to parse FRAMEBUFFER_WIDTH '{}': {} — exiting", v, e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ },
+ Err(e) => {
+ eprintln!("vesad: FRAMEBUFFER_WIDTH not readable: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
+ let height = match env::var("FRAMEBUFFER_HEIGHT") {
+ Ok(v) => match usize::from_str_radix(&v, 16) {
+ Ok(n) => n,
+ Err(e) => {
+ eprintln!("vesad: failed to parse FRAMEBUFFER_HEIGHT '{}': {} — exiting", v, e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ },
+ Err(e) => {
+ eprintln!("vesad: FRAMEBUFFER_HEIGHT not readable: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
+ let phys = match env::var("FRAMEBUFFER_ADDR") {
+ Ok(v) => match usize::from_str_radix(&v, 16) {
+ Ok(n) => n,
+ Err(e) => {
+ eprintln!("vesad: failed to parse FRAMEBUFFER_ADDR '{}': {} — exiting", v, e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ },
+ Err(e) => {
+ eprintln!("vesad: FRAMEBUFFER_ADDR not readable: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
+ let stride = match env::var("FRAMEBUFFER_STRIDE") {
+ Ok(v) => match usize::from_str_radix(&v, 16) {
+ Ok(n) => n,
+ Err(e) => {
+ eprintln!("vesad: failed to parse FRAMEBUFFER_STRIDE '{}': {} — exiting", v, e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ },
+ Err(e) => {
+ eprintln!("vesad: FRAMEBUFFER_STRIDE not readable: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -57 +97,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }];
+ let mut framebuffers = match unsafe { FrameBuffer::try_new(phys, width, height, stride) } {
+ Some(fb) => vec![fb],
+ None => {
+ eprintln!("vesad: failed to map primary framebuffer — exiting");
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
@@ -59,3 +106,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- //TODO: ideal maximum number of outputs?
- let bootloader_env = std::fs::read_to_string("/scheme/sys/env")
- .expect("failed to read env")
+ let bootloader_env = match std::fs::read_to_string("/scheme/sys/env") {
+ Ok(data) => data
@@ -63,3 +109,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .map(|line| {
- let (env, value) = line.split_once('=').unwrap();
- (env.to_owned(), value.to_owned())
+ .filter_map(|line| {
+ line.split_once('=')
+ .map(|(env, value)| (env.to_owned(), value.to_owned()))
@@ -67 +113,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .collect::<HashMap<String, String>>();
+ .collect::<HashMap<String, String>>(),
+ Err(e) => {
+ eprintln!("vesad: failed to read /scheme/sys/env: {} — continuing with primary framebuffer only", e);
+ HashMap::new()
+ }
+ };
@@ -96,4 +147,9 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let event_queue: EventQueue<Source> =
- EventQueue::new().expect("vesad: failed to create event queue");
- event_queue
- .subscribe(
+ let event_queue: EventQueue<Source> = match EventQueue::new() {
+ Ok(q) => q,
+ Err(e) => {
+ eprintln!("vesad: failed to create event queue: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ };
+ if let Err(e) = event_queue.subscribe(
@@ -103,4 +159,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- )
- .unwrap();
- event_queue
- .subscribe(
+ ) {
+ eprintln!("vesad: failed to subscribe input events: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
+ if let Err(e) = event_queue.subscribe(
@@ -110,2 +168,5 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- )
- .unwrap();
+ ) {
+ eprintln!("vesad: failed to subscribe scheme events: {} — exiting", e);
+ daemon.ready();
+ std::process::exit(0);
+ }
@@ -113 +174,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace");
+ if let Err(e) = libredox::call::setrens(0, 0) {
+ eprintln!("vesad: failed to enter null namespace: {} — continuing", e);
+ }
@@ -120 +183,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data))
+ .chain(event_queue.map(|e| match e {
+ Ok(ev) => ev.user_data,
+ Err(err) => {
+ eprintln!("vesad: event error: {} — continuing", err);
+ Source::Scheme
+ }
+ }))
@@ -125,3 +194,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- scheme
- .tick()
- .expect("vesad: failed to handle scheme events");
+ if let Err(e) = scheme.tick() {
+ eprintln!("vesad: scheme tick error: {} — continuing", e);
+ }
@@ -132 +201,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- panic!();
+ eprintln!("vesad: event loop ended unexpectedly — exiting");
+ std::process::exit(1);
diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs
index 5bf2be91..9ce6b89a 100644
--- a/drivers/graphics/vesad/src/scheme.rs
+++ b/drivers/graphics/vesad/src/scheme.rs
@@ -160 +160 @@ impl FrameBuffer {
- pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self {
+ pub unsafe fn try_new(phys: usize, width: usize, height: usize, stride: usize) -> Option<Self> {
@@ -162 +162 @@ impl FrameBuffer {
- let virt = common::physmap(
+ let virt = match common::physmap(
@@ -170,2 +170,7 @@ impl FrameBuffer {
- )
- .expect("vesad: failed to map framebuffer") as *mut u32;
+ ) {
+ Ok(v) => v as *mut u32,
+ Err(e) => {
+ eprintln!("vesad: failed to map framebuffer at 0x{phys:X}: {e}");
+ return None;
+ }
+ };
@@ -175 +180 @@ impl FrameBuffer {
- Self {
+ Some(Self {
@@ -181 +186 @@ impl FrameBuffer {
- }
+ })
@@ -205 +210 @@ impl FrameBuffer {
- Some(Self::new(phys, width, height, stride))
+ Self::try_new(phys, width, height, stride)
@@ -0,0 +1,22 @@
diff --git a/init.initfs.d/20_fbbootlogd.service b/init.initfs.d/20_fbbootlogd.service
index 5a8bf3c8..199c112a 100644
--- a/init.initfs.d/20_fbbootlogd.service
+++ b/init.initfs.d/20_fbbootlogd.service
@@ -6,0 +7,6 @@ cmd = "fbbootlogd"
+inherit_envs = [
+ "FRAMEBUFFER_ADDR",
+ "FRAMEBUFFER_WIDTH",
+ "FRAMEBUFFER_HEIGHT",
+ "FRAMEBUFFER_STRIDE",
+]
diff --git a/init.initfs.d/20_fbcond.service b/init.initfs.d/20_fbcond.service
index 8db3dfdb..e618b419 100644
--- a/init.initfs.d/20_fbcond.service
+++ b/init.initfs.d/20_fbcond.service
@@ -7,0 +8,6 @@ args = ["2"]
+inherit_envs = [
+ "FRAMEBUFFER_ADDR",
+ "FRAMEBUFFER_WIDTH",
+ "FRAMEBUFFER_HEIGHT",
+ "FRAMEBUFFER_STRIDE",
+]
+15
View File
@@ -0,0 +1,15 @@
c[?7lSeaBIOS (version Arch Linux 1.17.0-2-2)
iPXE (http://ipxe.org) 00:02.0 C900 PCI2.10 PnP PMM+7EFD3DD0+7EF33DD0 C900
Press Ctrl-B to configure iPXE (PCI 00:02.0)...
Booting from Hard Disk...
Stage 1
00000022#007F 0000:C000
000000A1#007F 0FE0:C000
00000120#007F 1FC0:C000
0000019F#007F 2FA0:C000
0000021E#0038 3F80:C000
+15
View File
@@ -0,0 +1,15 @@
c[?7lSeaBIOS (version Arch Linux 1.17.0-2-2)
iPXE (http://ipxe.org) 00:02.0 C900 PCI2.10 PnP PMM+7EFD3DD0+7EF33DD0 C900
Press Ctrl-B to configure iPXE (PCI 00:02.0)...
Booting from Hard Disk...
Stage 1
00000022#007F 0000:C000
000000A1#007F 0FE0:C000
00000120#007F 1FC0:C000
0000019F#007F 2FA0:C000
0000021E#0038 3F80:C000
+41338
View File
File diff suppressed because it is too large Load Diff
View File
View File
+7 -4
View File
@@ -6,8 +6,11 @@ template = "custom"
dependencies = [ dependencies = [
"redoxfs", "redoxfs",
"ion", "ion",
"driver-manager",
] ]
script = """ script = """
set -eo pipefail
BINS=( BINS=(
init init
logd logd
@@ -23,7 +26,6 @@ BINS=(
lived lived
nvmed nvmed
pcid pcid
pcid-spawner
rtcd rtcd
vesad vesad
) )
@@ -71,8 +73,8 @@ mkdir -p "${COOKBOOK_BUILD}/initfs/lib/init.d"
cp "${COOKBOOK_SOURCE}/init.initfs.d"/* "${COOKBOOK_BUILD}/initfs/lib/init.d/" cp "${COOKBOOK_SOURCE}/init.initfs.d"/* "${COOKBOOK_BUILD}/initfs/lib/init.d/"
mkdir -pv "${COOKBOOK_BUILD}/initfs/lib/pcid.d" mkdir -pv "${COOKBOOK_BUILD}/initfs/lib/drivers.d"
cp -v "${COOKBOOK_SOURCE}/drivers/initfs.toml" "${COOKBOOK_BUILD}/initfs/lib/pcid.d/initfs.toml" cp -v "${COOKBOOK_SOURCE}/drivers/initfs-storage.toml" "${COOKBOOK_BUILD}/initfs/lib/drivers.d/00-storage.toml"
export CARGO_PROFILE_RELEASE_OPT_LEVEL=s export CARGO_PROFILE_RELEASE_OPT_LEVEL=s
export CARGO_PROFILE_RELEASE_PANIC=abort export CARGO_PROFILE_RELEASE_PANIC=abort
@@ -85,7 +87,7 @@ mkdir -pv "${COOKBOOK_BUILD}/initfs/bin" "${COOKBOOK_BUILD}/initfs/lib/drivers"
for bin in "${BINS[@]}" for bin in "${BINS[@]}"
do do
case "${bin}" in case "${bin}" in
init | logd | ramfs | randd | zerod | pcid | pcid-spawner | fbbootlogd | fbcond | inputd | vesad | lived | ps2d | acpid | bcm2835-sdhcid | rtcd | hwd) init | logd | ramfs | randd | zerod | fbbootlogd | fbcond | inputd | vesad | lived | ps2d | acpid | bcm2835-sdhcid | rtcd | hwd | pcid)
cp -v "target/${TARGET}/${build_type}/${bin}" "${COOKBOOK_BUILD}/initfs/bin" cp -v "target/${TARGET}/${build_type}/${bin}" "${COOKBOOK_BUILD}/initfs/bin"
;; ;;
*) *)
@@ -96,6 +98,7 @@ done
cp "${COOKBOOK_SYSROOT}/usr/bin/redoxfs" "${COOKBOOK_BUILD}/initfs/bin" cp "${COOKBOOK_SYSROOT}/usr/bin/redoxfs" "${COOKBOOK_BUILD}/initfs/bin"
cp "${COOKBOOK_SYSROOT}/usr/bin/ion" "${COOKBOOK_BUILD}/initfs/bin" cp "${COOKBOOK_SYSROOT}/usr/bin/ion" "${COOKBOOK_BUILD}/initfs/bin"
cp "${COOKBOOK_SYSROOT}/usr/bin/driver-manager" "${COOKBOOK_BUILD}/initfs/bin"
ARCH="$(echo "${GNU_TARGET}" | cut -d - -f1)" ARCH="$(echo "${GNU_TARGET}" | cut -d - -f1)"
RUSTFLAGS="$RUSTFLAGS -Ctarget-feature=+crt-static -Clink-arg=-nostartfiles -Clink-arg=-nostdlib" cargo \ RUSTFLAGS="$RUSTFLAGS -Ctarget-feature=+crt-static -Clink-arg=-nostartfiles -Clink-arg=-nostdlib" cargo \
+2
View File
@@ -77,6 +77,8 @@ patches = [
"P30-acpid-graceful-scheme-exists.patch", "P30-acpid-graceful-scheme-exists.patch",
"P31-xhcid-restore-interrupts.patch", "P31-xhcid-restore-interrupts.patch",
"P32-acpid-graceful-boot.patch", "P32-acpid-graceful-boot.patch",
"P33-vesad-graceful-boot.patch",
"P34-fbcond-fbbootlogd-env.patch",
] ]
[package] [package]
@@ -185,6 +185,16 @@ pub(super) fn init(madt: Madt) {
} }
} }
// Detect whether MADT contains any LocalX2Apic entries.
// Some firmware (notably QEMU and some older BIOS) provides only 8-bit
// LocalApic entries even when the CPU supports x2APIC. In that case we must
// fall back to processing LocalApic entries with zero-extended IDs.
let has_x2apic_entries = madt.iter().any(|e| matches!(e, MadtEntry::LocalX2Apic(_)));
let x2apic_fallback = local_apic.x2 && !has_x2apic_entries;
if x2apic_fallback {
warn!("MADT: x2APIC mode active but no LocalX2Apic entries found; falling back to LocalApic entries with zero-extended IDs");
}
unsafe { unsafe {
let preliminary_cpu_count = madt let preliminary_cpu_count = madt
.iter() .iter()
@@ -194,6 +204,9 @@ pub(super) fn init(madt: Madt) {
MadtEntry::LocalApic(local) if !local_apic.x2 => { MadtEntry::LocalApic(local) if !local_apic.x2 => {
u32::from(local.id) == me.get() || local.flags & 1 == 1 u32::from(local.id) == me.get() || local.flags & 1 == 1
} }
MadtEntry::LocalApic(local) if local_apic.x2 && x2apic_fallback => {
u32::from(local.id) == me.get() || local.flags & 1 == 1
}
MadtEntry::LocalApic(_) => false, MadtEntry::LocalApic(_) => false,
// xAPIC mode: cannot use 32-bit x2APIC IDs via 8-bit ICR. // xAPIC mode: cannot use 32-bit x2APIC IDs via 8-bit ICR.
// Skip LocalX2Apic entries and use LocalApic exclusively. // Skip LocalX2Apic entries and use LocalApic exclusively.
@@ -222,10 +235,15 @@ pub(super) fn init(madt: Madt) {
} }
} }
MadtEntry::LocalApic(local) if local.flags & 1 == 1 && local_apic.x2 => { MadtEntry::LocalApic(local) if local.flags & 1 == 1 && local_apic.x2 => {
// x2APIC mode: skip 8-bit LocalApic IDs; they conflict with if x2apic_fallback {
// 32-bit x2APIC IDs. Dedup only among LocalX2Apic entries. let id = u32::from(local.id);
if !seen_apic_ids.insert(id) {
warn!("MADT: duplicate APIC ID {} in LocalApic entry (x2APIC fallback), firmware bug", id);
}
} else {
debug!("MADT: ignoring 8-bit LocalApic ID {} in x2APIC mode", local.id); debug!("MADT: ignoring 8-bit LocalApic ID {} in x2APIC mode", local.id);
} }
}
MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 && local_apic.x2 => { MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 && local_apic.x2 => {
let id = local.x2apic_id; let id = local.x2apic_id;
if !seen_apic_ids.insert(id) { if !seen_apic_ids.insert(id) {
@@ -249,11 +267,142 @@ pub(super) fn init(madt: Madt) {
// the BSP's 32-bit x2APIC ID. All entries would be treated as APs, // the BSP's 32-bit x2APIC ID. All entries would be treated as APs,
// and SIPI would target the wrong processors. Skip them and rely // and SIPI would target the wrong processors. Skip them and rely
// on LocalX2Apic entries exclusively. // on LocalX2Apic entries exclusively.
if local_apic.x2 { if local_apic.x2 && !x2apic_fallback {
debug!( debug!(
" Skipping 8-bit LocalApic id={} (x2APIC active, using LocalX2Apic entries)", " Skipping 8-bit LocalApic id={} (x2APIC active, using LocalX2Apic entries)",
ap_local_apic.id ap_local_apic.id
); );
} else if local_apic.x2 && x2apic_fallback {
let apic_id = u32::from(ap_local_apic.id);
if apic_id == me.get() {
debug!(" This is my local APIC (x2APIC fallback, id={})", apic_id);
} else if ap_local_apic.flags & 1 == 1 {
let alloc = match allocate_p2frame(4) {
Some(frame) => frame,
None => {
println!("KERNEL AP: CPU {} no memory for stack, skipping", apic_id);
continue;
}
};
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
let stack_end = stack_start + (PAGE_SIZE << 4);
let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
println!(
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
apic_id
);
continue;
}
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
let args = KernelArgsAp {
stack_end: stack_end as *mut u8,
cpu_id,
pcr_ptr,
idt_ptr,
};
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
let ap_args_ptr = unsafe { ap_ready.add(1) };
let ap_page_table = unsafe { ap_ready.add(2) };
let ap_code = unsafe { ap_ready.add(3) };
unsafe {
ap_ready.write(0);
ap_args_ptr.write(&args as *const _ as u64);
ap_page_table.write(page_table_physaddr as u64);
#[expect(clippy::fn_to_numeric_cast)]
ap_code.write(kstart_ap as u64);
core::sync::atomic::fence(Ordering::SeqCst);
};
AP_READY.store(false, Ordering::SeqCst);
// Clear APIC Error Status Register before starting AP.
unsafe { local_apic.esr(); }
// Send INIT IPI (Assert) — x2APIC uses 64-bit ICR format.
{
let mut icr = 0x4500u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
// Intel SDM Vol 3A §8.4.4: wait 10ms after INIT deassert
early_udelay(10_000);
// Send START IPI #1
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
let mut icr = 0x0600 | ap_segment as u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
early_udelay(200);
// Send START IPI #2 (recommended for compatibility)
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
let mut icr = 0x0600 | ap_segment as u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
early_udelay(200);
// Check ESR for delivery errors after SIPI sequence.
let esr_val = unsafe { local_apic.esr() };
if esr_val != 0 {
println!(
"KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
apic_id, esr_val
);
}
let mut trampoline_ready = false;
for _ in 0..AP_SPIN_LIMIT {
if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
trampoline_ready = true;
break;
}
hint::spin_loop();
}
if !trampoline_ready {
println!("KERNEL AP: CPU {} trampoline timeout, skipping", apic_id);
continue;
}
let mut kernel_ready = false;
for _ in 0..AP_SPIN_LIMIT {
if AP_READY.load(Ordering::SeqCst) {
kernel_ready = true;
break;
}
hint::spin_loop();
}
if !kernel_ready {
println!("KERNEL AP: CPU {} AP_READY timeout, skipping", apic_id);
continue;
}
// Record APIC→CPU mapping for NUMA topology.
unsafe {
record_apic_mapping(apic_id, cpu_id);
}
// Set NUMA node from SRAT data.
if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
if let Some(node) = crate::acpi::srat::numa_node_for_apic(apic_id) {
percpu.numa_node.set(node);
}
}
RmmA::invalidate_all();
}
} else if u32::from(ap_local_apic.id) == me.get() { } else if u32::from(ap_local_apic.id) == me.get() {
debug!(" This is my local APIC"); debug!(" This is my local APIC");
} else if ap_local_apic.flags & 1 == 1 { } else if ap_local_apic.flags & 1 == 1 {
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Ensure cargo bin (cbindgen, rustup, etc.) is in PATH
case ":${PATH}:" in
*":$HOME/.cargo/bin:"*) ;;
*) export PATH="$HOME/.cargo/bin:$PATH" ;;
esac
#qemu-system-x86_64 -m 8G -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/RedBear-OS/build/x86_64/redbear-mini.iso,format=raw -device virtio-gpu-pci -enable-kvm -serial mon:stdio
qemu-system-x86_64 -d guest_errors -name "Red Bear OS x86_64" -device qemu-xhci -smp 4 -m 2048 -bios /usr/share/edk2/x64/OVMF.4m.fd -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device e1000,netdev=net0,id=nic0 -netdev user,id=net0 -nographic -drive file=build/x86_64/redbear-mini/harddrive.img,format=raw,if=none,id=drv0 -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=build/x86_64/redbear-mini/extra.img,format=raw,if=none,id=drv1 -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host 2>&1 | tee /tmp/qemu-boot-full.log | tail -n 100