Phase 4.4 : binaire prêt pour test sur image bootée
Modifications du test display-backend pour le runtime VT-handler réel : - DebugSink wrapper qui mirroir stdout vers /scheme/debug (serial host stdio) → la sortie est visible côté host même si on tourne sur un VT non actif - Lecture env var VT (mise par le init Redox) - Appel `inputd -A <vt>` après open_display, comme Orbital le fait (cf orbital/src/main.rs ligne ~43) - Sleep 500 ms en fin de main pour laisser le serial flush README enrichi avec les étapes Voie B précises (commande redoxfs locale, clavier FR via QEMU_USER_FLAGS, switch VT Ctrl+Alt+F2, login root/password). Note locale (non versionnée) : ajout d'un hook QEMU_USER_FLAGS dans ~/Projets/Redox/redox-src/mk/qemu.mk pour passer des args qemu user-supplied. Leyoda 2026 – GPLv3
This commit is contained in:
parent
c6ad583a72
commit
9ceb9a04fc
2 changed files with 120 additions and 50 deletions
39
README.md
39
README.md
|
|
@ -76,21 +76,40 @@ Ouvre une fenêtre QEMU, boote une mini-VM Redox avec framebuffer, lance le bina
|
||||||
À fermer manuellement quand le test est fini.
|
À fermer manuellement quand le test est fini.
|
||||||
|
|
||||||
### Voie B — Image complète + `make qemu`
|
### Voie B — Image complète + `make qemu`
|
||||||
Pousser le binaire dans une image Redox bootable via redoxfs-fuse :
|
|
||||||
|
Pousser le binaire dans une image Redox bootable via redoxfs-fuse, puis booter
|
||||||
|
en mode interactif. Étapes :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Monter l'image
|
# 1. Monter l'image (redoxfs reste en foreground tant que monté)
|
||||||
sudo modprobe fuse
|
|
||||||
mkdir -p /tmp/redox-mnt
|
mkdir -p /tmp/redox-mnt
|
||||||
redoxfs ~/Projets/Redox/redox-src/build/x86_64/desktop/harddrive.img /tmp/redox-mnt
|
~/Projets/Redox/redox-src/build/fstools/bin/redoxfs \
|
||||||
# Copier le binaire
|
~/Projets/Redox/redox-src/build/x86_64/desktop/harddrive.img \
|
||||||
sudo cp target/x86_64-unknown-redox/release/redox-wl-test-display-backend /tmp/redox-mnt/usr/bin/
|
/tmp/redox-mnt &
|
||||||
# Démonter
|
sleep 2
|
||||||
|
|
||||||
|
# 2. Copier le binaire dans /usr/bin de l'image
|
||||||
|
cp ~/Projets/Redox/redox-wayland-compositor/crates/redox-wl-test-display-backend/target/x86_64-unknown-redox/release/redox-wl-test-display-backend \
|
||||||
|
/tmp/redox-mnt/usr/bin/
|
||||||
|
|
||||||
|
# 3. Démonter
|
||||||
fusermount -u /tmp/redox-mnt
|
fusermount -u /tmp/redox-mnt
|
||||||
# Booter
|
rmdir /tmp/redox-mnt
|
||||||
cd ~/Projets/Redox/redox-src && make qemu audio=no
|
|
||||||
# Dans Redox : login user / password root, puis lancer le binaire
|
# 4. Booter (avec clavier FR si besoin, voir hook QEMU_USER_FLAGS dans mk/qemu.mk)
|
||||||
|
cd ~/Projets/Redox/redox-src
|
||||||
|
make qemu audio=no QEMU_USER_FLAGS="-k fr"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Une fois Redox bootée :
|
||||||
|
- **Ctrl+Alt+F2** pour basculer sur le **VT 2 (console texte)**, sans Orbital
|
||||||
|
- Login : `root` / mot de passe `password`
|
||||||
|
- Lancer : `redox-wl-test-display-backend`
|
||||||
|
|
||||||
|
> Le binaire écrit aussi sur `/scheme/debug` (serial), donc même si tu lances
|
||||||
|
> depuis Orbital (VT 3) la sortie sera visible côté host dans le terminal qui
|
||||||
|
> a lancé `make qemu`.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
Voir `REDOX_COSMIC_XWAYLAND_RS_PLAN.md` pour le plan complet 14 phases / 5 ans.
|
Voir `REDOX_COSMIC_XWAYLAND_RS_PLAN.md` pour le plan complet 14 phases / 5 ans.
|
||||||
|
|
|
||||||
|
|
@ -21,43 +21,91 @@
|
||||||
//! parce qu'Orbital tient déjà le display. Ce test reste lecture seule à ce
|
//! parce qu'Orbital tient déjà le display. Ce test reste lecture seule à ce
|
||||||
//! stade — phase 4 vraie consiste à *prendre la place* d'Orbital.
|
//! stade — phase 4 vraie consiste à *prendre la place* d'Orbital.
|
||||||
|
|
||||||
use std::io;
|
use std::env;
|
||||||
use std::process::ExitCode;
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::process::{Command, ExitCode};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use drm::Device as _;
|
use drm::Device as _;
|
||||||
use drm::control::{Device as _, connector::State};
|
use drm::control::{Device as _, connector::State};
|
||||||
use graphics_ipc::V2GraphicsHandle;
|
use graphics_ipc::V2GraphicsHandle;
|
||||||
use inputd::ConsumerHandle;
|
use inputd::ConsumerHandle;
|
||||||
|
|
||||||
|
/// Sink that mirrors stdout to /scheme/debug so the host serial console
|
||||||
|
/// captures every diagnostic line without having to be on the active VT.
|
||||||
|
struct DebugSink(std::sync::Mutex<Option<std::fs::File>>);
|
||||||
|
|
||||||
|
impl DebugSink {
|
||||||
|
fn new() -> Self {
|
||||||
|
let f = OpenOptions::new().write(true).open("/scheme/debug").ok();
|
||||||
|
DebugSink(std::sync::Mutex::new(f))
|
||||||
|
}
|
||||||
|
fn writeln(&self, s: &str) {
|
||||||
|
println!("{s}");
|
||||||
|
if let Ok(mut g) = self.0.lock() {
|
||||||
|
if let Some(f) = g.as_mut() {
|
||||||
|
let _ = writeln!(f, "{s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dlog(s: &str) {
|
||||||
|
static SINK: OnceLock<DebugSink> = OnceLock::new();
|
||||||
|
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||||
|
}
|
||||||
|
|
||||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("[disp] Phase 4 display backend test on Redox");
|
dlog("[disp] Phase 4 display backend test on Redox");
|
||||||
|
|
||||||
|
// VT is set by init when we're started as a VT handler (cf Orbital's main.rs).
|
||||||
|
let vt = env::var("VT").ok();
|
||||||
|
dlog(&format!("[disp] VT env = {:?}", vt));
|
||||||
|
|
||||||
// Step 1: open consumer handle (talks to inputd)
|
// Step 1: open consumer handle (talks to inputd)
|
||||||
let consumer = ConsumerHandle::new_vt().map_err(|e| {
|
let consumer = ConsumerHandle::new_vt().map_err(|e| {
|
||||||
format!(
|
format!(
|
||||||
"ConsumerHandle::new_vt failed (expected in headless redoxer): {e} \
|
"ConsumerHandle::new_vt failed: {e} (errno {})",
|
||||||
(errno {})",
|
|
||||||
io::Error::last_os_error().raw_os_error().unwrap_or(0)
|
io::Error::last_os_error().raw_os_error().unwrap_or(0)
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
println!("[disp] inputd consumer handle opened");
|
dlog("[disp] inputd consumer handle opened");
|
||||||
|
|
||||||
// Step 2: open display via inputd's fpath redirection
|
// Step 2: open display via inputd's fpath redirection
|
||||||
let display_file = consumer.open_display_v2().map_err(|e| {
|
let display_file = consumer
|
||||||
format!(
|
.open_display_v2()
|
||||||
"open_display_v2 failed (expected in headless redoxer): {e}"
|
.map_err(|e| format!("open_display_v2 failed: {e}"))?;
|
||||||
)
|
dlog("[disp] display file opened from inputd path");
|
||||||
})?;
|
|
||||||
println!("[disp] display file opened from inputd path");
|
|
||||||
|
|
||||||
// Step 3: wrap as V2GraphicsHandle (subset DRM Linux)
|
// Step 3: register as VT handler with inputd, exactly like Orbital does
|
||||||
|
// (cf orbital/src/main.rs ~line 43). This must happen AFTER the display is
|
||||||
|
// opened so inputd considers us authoritative for that VT.
|
||||||
|
if let Some(v) = &vt {
|
||||||
|
match Command::new("inputd").arg("-A").arg(v).status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
dlog(&format!("[disp] registered with inputd -A {v}"));
|
||||||
|
}
|
||||||
|
Ok(status) => {
|
||||||
|
dlog(&format!("[disp] inputd -A {v} exit {:?}", status));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dlog(&format!("[disp] inputd -A {v} spawn err: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: wrap as V2GraphicsHandle (subset DRM Linux)
|
||||||
let handle = V2GraphicsHandle::from_file(display_file)?;
|
let handle = V2GraphicsHandle::from_file(display_file)?;
|
||||||
println!("[disp] V2GraphicsHandle created");
|
dlog("[disp] V2GraphicsHandle created");
|
||||||
|
|
||||||
// Step 4: enumerate connectors
|
// Step 5: enumerate connectors
|
||||||
let resources = handle.resource_handles()?;
|
let resources = handle.resource_handles()?;
|
||||||
let connectors = resources.connectors();
|
let connectors = resources.connectors();
|
||||||
println!("[disp] {} connector(s) reported by KMS subset", connectors.len());
|
dlog(&format!(
|
||||||
|
"[disp] {} connector(s) reported by KMS subset",
|
||||||
|
connectors.len()
|
||||||
|
));
|
||||||
|
|
||||||
let mut connected_count = 0;
|
let mut connected_count = 0;
|
||||||
for (i, &conn) in connectors.iter().enumerate() {
|
for (i, &conn) in connectors.iter().enumerate() {
|
||||||
|
|
@ -65,22 +113,25 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Ok(info) => {
|
Ok(info) => {
|
||||||
let state = info.state();
|
let state = info.state();
|
||||||
let modes = info.modes();
|
let modes = info.modes();
|
||||||
println!(
|
dlog(&format!(
|
||||||
"[disp] #{i} connector {:?}: state={:?}, {} mode(s)",
|
"[disp] #{i} connector {:?}: state={:?}, {} mode(s)",
|
||||||
conn,
|
conn,
|
||||||
state,
|
state,
|
||||||
modes.len()
|
modes.len()
|
||||||
);
|
));
|
||||||
if state == State::Connected {
|
if state == State::Connected {
|
||||||
connected_count += 1;
|
connected_count += 1;
|
||||||
if let Some(mode) = modes.first() {
|
if let Some(mode) = modes.first() {
|
||||||
let (w, h) = mode.size();
|
let (w, h) = mode.size();
|
||||||
println!("[disp] first mode: {w}x{h}");
|
dlog(&format!("[disp] first mode: {w}x{h}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("[disp] #{i} connector {:?}: get_connector failed: {e}", conn);
|
dlog(&format!(
|
||||||
|
"[disp] #{i} connector {:?}: get_connector failed: {e}",
|
||||||
|
conn
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,54 +139,54 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if connected_count == 0 {
|
if connected_count == 0 {
|
||||||
return Err("no connected display found".into());
|
return Err("no connected display found".into());
|
||||||
}
|
}
|
||||||
println!("[disp] {connected_count} connected display(s)");
|
dlog(&format!("[disp] {connected_count} connected display(s)"));
|
||||||
|
|
||||||
// Step 5: capabilities probe
|
// Step 6: capabilities probe
|
||||||
use drm::DriverCapability;
|
use drm::DriverCapability;
|
||||||
let dumb = handle.get_driver_capability(DriverCapability::DumbBuffer);
|
let dumb = handle.get_driver_capability(DriverCapability::DumbBuffer);
|
||||||
let cursor_w = handle.get_driver_capability(DriverCapability::CursorWidth);
|
let cursor_w = handle.get_driver_capability(DriverCapability::CursorWidth);
|
||||||
let cursor_h = handle.get_driver_capability(DriverCapability::CursorHeight);
|
let cursor_h = handle.get_driver_capability(DriverCapability::CursorHeight);
|
||||||
println!(
|
dlog(&format!(
|
||||||
"[disp] driver caps: dumb_buffer={:?} cursor={}x{}",
|
"[disp] driver caps: dumb_buffer={:?} cursor={}x{}",
|
||||||
dumb,
|
dumb,
|
||||||
cursor_w.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()),
|
cursor_w.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()),
|
||||||
cursor_h.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()),
|
cursor_h.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()),
|
||||||
);
|
));
|
||||||
|
|
||||||
// Step 6: try allocating a CpuBackedBuffer of a small size, paint a pattern,
|
// Step 7: allocate a small CpuBackedBuffer, paint a pattern, destroy.
|
||||||
// and destroy it. This validates the buffer pipeline of graphics-ipc without
|
|
||||||
// setting it as scanout (which would require a CRTC takeover from Orbital).
|
|
||||||
use drm::buffer::DrmFourcc;
|
use drm::buffer::DrmFourcc;
|
||||||
use graphics_ipc::CpuBackedBuffer;
|
use graphics_ipc::CpuBackedBuffer;
|
||||||
let (test_w, test_h) = (64u32, 64u32);
|
let (test_w, test_h) = (64u32, 64u32);
|
||||||
let mut buf = CpuBackedBuffer::new(&handle, (test_w, test_h), DrmFourcc::Argb8888, 32)?;
|
let mut buf = CpuBackedBuffer::new(&handle, (test_w, test_h), DrmFourcc::Argb8888, 32)?;
|
||||||
println!(
|
dlog(&format!(
|
||||||
"[disp] CpuBackedBuffer allocated {}x{} ARGB8888 (shadow={})",
|
"[disp] CpuBackedBuffer allocated {}x{} ARGB8888 (shadow={})",
|
||||||
test_w,
|
test_w,
|
||||||
test_h,
|
test_h,
|
||||||
buf.has_shadow_buf()
|
buf.has_shadow_buf()
|
||||||
);
|
));
|
||||||
let bytes = buf.shadow_buf();
|
let bytes = buf.shadow_buf();
|
||||||
for (i, b) in bytes.iter_mut().enumerate() {
|
for (i, b) in bytes.iter_mut().enumerate() {
|
||||||
*b = (i & 0xFF) as u8;
|
*b = (i & 0xFF) as u8;
|
||||||
}
|
}
|
||||||
buf.sync_rect(0, 0, test_w, test_h);
|
buf.sync_rect(0, 0, test_w, test_h);
|
||||||
println!("[disp] painted test pattern + sync_rect");
|
dlog("[disp] painted test pattern + sync_rect");
|
||||||
buf.destroy(&handle)?;
|
buf.destroy(&handle)?;
|
||||||
println!("[disp] CpuBackedBuffer destroyed");
|
dlog("[disp] CpuBackedBuffer destroyed");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
match run() {
|
let res = run();
|
||||||
Ok(()) => {
|
match &res {
|
||||||
println!("[disp] PASS: display backend pipeline reachable");
|
Ok(()) => dlog("[disp] PASS: display backend pipeline reachable"),
|
||||||
ExitCode::SUCCESS
|
Err(e) => dlog(&format!("[disp] FAIL: {e}")),
|
||||||
}
|
}
|
||||||
Err(e) => {
|
// sleep briefly so the serial gets a chance to flush before the OS may shut down
|
||||||
eprintln!("[disp] FAIL: {e}");
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
|
if res.is_ok() {
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
} else {
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue