P0..P71 baseline before fbcond/fbbootlogd init fix
This commit is contained in:
@@ -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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -649,19 +651,39 @@ does NOT strip timestamps — they should be removed from the patch file directl
|
||||
|
||||
### 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)
|
||||
are the single largest source of patch application failures. Use the zero-context,
|
||||
whitespace-ignored technique to make patches resilient to drift:
|
||||
|
||||
**Generate:**
|
||||
**Workflow (mandatory):**
|
||||
```bash
|
||||
# 1. Start with a clean P0..P(N-1) source tree (repo fetch already applied earlier patches)
|
||||
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
|
||||
|
||||
# 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
|
||||
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:**
|
||||
@@ -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
|
||||
- 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:
|
||||
- A variable was renamed (e.g., `deamon` → `daemon` in context)
|
||||
- Lines were added or removed above the changed code
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ path = "/usr/lib/init.d/10_acid.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Acid test runner"
|
||||
requires_weak = ["00_pcid-spawner.service"]
|
||||
requires_weak = ["00_driver-manager.service"]
|
||||
|
||||
[service]
|
||||
cmd = "ion"
|
||||
|
||||
@@ -380,8 +380,8 @@ vendor = 0xFFFF
|
||||
device = 0xFFFF
|
||||
"""
|
||||
|
||||
# Profiles that include this fragment should start `driver-manager` instead of
|
||||
# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself.
|
||||
# driver-manager owns PCI device enumeration, driver matching, and bind/channel
|
||||
# handoff — replacing the old pcid + pcid-spawner pair entirely.
|
||||
[[files]]
|
||||
path = "/etc/init.d/00_driver-manager.service"
|
||||
data = """
|
||||
|
||||
@@ -447,8 +447,9 @@ path = "/etc/init.d/29_activate_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Activate fallback console VT"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"08_userland.target",
|
||||
"00_base.target",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -462,6 +463,7 @@ path = "/etc/init.d/30_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Console terminals"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"29_activate_console.service",
|
||||
]
|
||||
@@ -477,6 +479,7 @@ path = "/etc/init.d/31_debug_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Debug console on serial port"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"29_activate_console.service",
|
||||
]
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
# 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
|
||||
# 00_ipcd.service and 00_ptyd.service from the base recipe.
|
||||
# 00_drivers / 10_net: no longer overridden — the legacy scripts were removed
|
||||
# from base.toml. The retained 00_pcid-spawner.service unit name now
|
||||
# launches driver-manager so existing init ordering remains stable.
|
||||
# 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.
|
||||
# 00_pcid-spawner.service has been fully replaced by 00_driver-manager.service
|
||||
# (defined in redbear-device-services.toml). The old pcid-spawner
|
||||
# unit name is no longer used anywhere.
|
||||
|
||||
[packages]
|
||||
zsh = {}
|
||||
@@ -38,16 +33,3 @@ default_dependencies = false
|
||||
cmd = "audiod"
|
||||
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
@@ -27,9 +27,8 @@ redbear-release = {}
|
||||
redbear-hwutils = {}
|
||||
redbear-quirks = {}
|
||||
|
||||
# Device driver infrastructure: driver-manager is started by
|
||||
# redbear-device-services.toml, with 00_pcid-spawner.service retained only as a
|
||||
# compatibility dependency alias for older service units.
|
||||
# Device driver infrastructure: driver-manager replaces pcid-spawner;
|
||||
# 00_driver-manager.service is defined in redbear-device-services.toml.
|
||||
ehcid = {}
|
||||
ohcid = {}
|
||||
uhcid = {}
|
||||
@@ -99,7 +98,7 @@ meson = {}
|
||||
ninja-build = {}
|
||||
m4 = {}
|
||||
#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
|
||||
|
||||
# ── Build / packaging utilities ──
|
||||
@@ -473,23 +472,7 @@ data = ""
|
||||
directory = true
|
||||
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]]
|
||||
path = "/lib/drivers.d/30-graphics.toml"
|
||||
@@ -508,6 +491,7 @@ path = "/etc/init.d/29_activate_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Activate console VT"
|
||||
default_dependencies = false
|
||||
requires_weak = ["00_base.target"]
|
||||
|
||||
[service]
|
||||
@@ -521,6 +505,7 @@ path = "/etc/init.d/30_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Console terminals"
|
||||
default_dependencies = false
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
@@ -534,6 +519,7 @@ path = "/etc/init.d/31_debug_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Debug console"
|
||||
default_dependencies = false
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
@@ -541,3 +527,16 @@ cmd = "getty"
|
||||
args = ["/scheme/debug/no-preserve", "-J"]
|
||||
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
@@ -21,7 +21,7 @@ path = "/usr/lib/init.d/10_smolnetd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Network stack for redoxer"
|
||||
requires_weak = ["00_pcid-spawner.service"]
|
||||
requires_weak = ["00_driver-manager.service"]
|
||||
|
||||
[service]
|
||||
cmd = "netstack"
|
||||
|
||||
@@ -5,3 +5,30 @@ index bb512c60..3c3ed97d 100644
|
||||
@@ -4 +4 @@ default_dependencies = false
|
||||
-requires_weak = ["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",
|
||||
+]
|
||||
@@ -0,0 +1,15 @@
|
||||
c[?7l[2J[0mSeaBIOS (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
|
||||
@@ -0,0 +1,15 @@
|
||||
c[?7l[2J[0mSeaBIOS (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
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,11 @@ template = "custom"
|
||||
dependencies = [
|
||||
"redoxfs",
|
||||
"ion",
|
||||
"driver-manager",
|
||||
]
|
||||
script = """
|
||||
set -eo pipefail
|
||||
|
||||
BINS=(
|
||||
init
|
||||
logd
|
||||
@@ -23,7 +26,6 @@ BINS=(
|
||||
lived
|
||||
nvmed
|
||||
pcid
|
||||
pcid-spawner
|
||||
rtcd
|
||||
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/"
|
||||
|
||||
mkdir -pv "${COOKBOOK_BUILD}/initfs/lib/pcid.d"
|
||||
cp -v "${COOKBOOK_SOURCE}/drivers/initfs.toml" "${COOKBOOK_BUILD}/initfs/lib/pcid.d/initfs.toml"
|
||||
mkdir -pv "${COOKBOOK_BUILD}/initfs/lib/drivers.d"
|
||||
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_PANIC=abort
|
||||
@@ -85,7 +87,7 @@ mkdir -pv "${COOKBOOK_BUILD}/initfs/bin" "${COOKBOOK_BUILD}/initfs/lib/drivers"
|
||||
for bin in "${BINS[@]}"
|
||||
do
|
||||
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"
|
||||
;;
|
||||
*)
|
||||
@@ -96,6 +98,7 @@ done
|
||||
|
||||
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/driver-manager" "${COOKBOOK_BUILD}/initfs/bin"
|
||||
|
||||
ARCH="$(echo "${GNU_TARGET}" | cut -d - -f1)"
|
||||
RUSTFLAGS="$RUSTFLAGS -Ctarget-feature=+crt-static -Clink-arg=-nostartfiles -Clink-arg=-nostdlib" cargo \
|
||||
|
||||
@@ -77,6 +77,8 @@ patches = [
|
||||
"P30-acpid-graceful-scheme-exists.patch",
|
||||
"P31-xhcid-restore-interrupts.patch",
|
||||
"P32-acpid-graceful-boot.patch",
|
||||
"P33-vesad-graceful-boot.patch",
|
||||
"P34-fbcond-fbbootlogd-env.patch",
|
||||
]
|
||||
|
||||
[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 {
|
||||
let preliminary_cpu_count = madt
|
||||
.iter()
|
||||
@@ -194,6 +204,9 @@ pub(super) fn init(madt: Madt) {
|
||||
MadtEntry::LocalApic(local) if !local_apic.x2 => {
|
||||
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,
|
||||
// xAPIC mode: cannot use 32-bit x2APIC IDs via 8-bit ICR.
|
||||
// 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 => {
|
||||
// x2APIC mode: skip 8-bit LocalApic IDs; they conflict with
|
||||
// 32-bit x2APIC IDs. Dedup only among LocalX2Apic entries.
|
||||
if x2apic_fallback {
|
||||
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);
|
||||
}
|
||||
}
|
||||
MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 && local_apic.x2 => {
|
||||
let id = local.x2apic_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,
|
||||
// and SIPI would target the wrong processors. Skip them and rely
|
||||
// on LocalX2Apic entries exclusively.
|
||||
if local_apic.x2 {
|
||||
if local_apic.x2 && !x2apic_fallback {
|
||||
debug!(
|
||||
" Skipping 8-bit LocalApic id={} (x2APIC active, using LocalX2Apic entries)",
|
||||
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() {
|
||||
debug!(" This is my local APIC");
|
||||
} else if ap_local_apic.flags & 1 == 1 {
|
||||
|
||||
Executable
+11
@@ -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
|
||||
Reference in New Issue
Block a user