Skip to main content

strat9_kernel/arch/x86_64/
vgabuf.rs

1//! Bounded, allocation-free SMP queue between logging and framebuffer rendering.
2//! Formatting/rendering never retains a reference to a released queue slot.
3
4#[path = "vga/log_queue.rs"]
5mod log_queue;
6use core::sync::atomic::{AtomicBool, Ordering};
7use log_queue::{Line, LogQueue};
8
9static CONSOLE_TASK_ACTIVE: AtomicBool = AtomicBool::new(false);
10
11pub fn console_task_active() -> bool {
12    CONSOLE_TASK_ACTIVE.load(Ordering::Acquire)
13}
14
15pub const VGABUF_CAPACITY: usize = log_queue::CAPACITY;
16pub const VGABUF_LINE_LEN: usize = log_queue::LINE_LEN;
17static QUEUE: LogQueue = LogQueue::new();
18const FLUSH_BATCH: usize = 30;
19
20pub fn vgabuf_write(line: &[u8]) {
21    QUEUE.push(line);
22}
23pub fn vgabuf_total_written() -> usize {
24    QUEUE.written()
25}
26pub fn vgabuf_total_dropped() -> usize {
27    QUEUE.dropped()
28}
29
30fn write_line(writer: &mut impl core::fmt::Write, line: &Line) {
31    if let Ok(s) = core::str::from_utf8(line.as_bytes()) {
32        let _ = writer.write_str(s);
33        if !s.ends_with('\n') {
34            let _ = writer.write_str("\n");
35        }
36    } else {
37        let _ = writer.write_str("<non-utf8>\n");
38    }
39}
40
41/// Render a bounded batch. Retrying pending presents also displays the last
42/// keyboard echo/log fragment when no subsequent message arrives.
43pub fn vgabuf_flush_to_framebuffer() {
44    if !crate::arch::x86_64::vga::is_available() {
45        vgabuf_drain_discard();
46        return;
47    }
48    let Some(mut writer) = crate::arch::x86_64::vga::VGA_WRITER.try_lock() else {
49        return;
50    };
51    let mut flushed = 0;
52    for _ in 0..FLUSH_BATCH {
53        let Some(line) = QUEUE.pop() else {
54            break;
55        };
56        write_line(&mut *writer, &line);
57        flushed += 1;
58    }
59    if flushed > 0 {
60        writer.flush_display();
61    } else {
62        writer.present_if_due(false);
63    }
64}
65
66fn vgabuf_drain_discard() {
67    for _ in 0..VGABUF_CAPACITY {
68        if QUEUE.pop().is_none() {
69            break;
70        }
71    }
72}
73
74pub fn vgabuf_flush_all() {
75    if !crate::arch::x86_64::vga::is_available() {
76        vgabuf_drain_discard();
77        return;
78    }
79    let Some(mut writer) = crate::arch::x86_64::vga::VGA_WRITER.try_lock() else {
80        vgabuf_flush_all_direct();
81        return;
82    };
83    for _ in 0..VGABUF_CAPACITY {
84        let Some(line) = QUEUE.pop() else {
85            break;
86        };
87        write_line(&mut *writer, &line);
88    }
89    writer.present_if_due(true);
90}
91
92fn vgabuf_flush_all_direct() {
93    // Own the bytes until drawing completes; released slots may immediately be
94    // reused on another CPU. Limit scratch storage to about 4 KiB in panic context.
95    const MAX_LINES: usize = 16;
96    let mut owned = [Line::EMPTY; MAX_LINES];
97    let mut count = 0;
98    for slot in &mut owned {
99        let Some(line) = QUEUE.pop() else {
100            break;
101        };
102        *slot = line;
103        count += 1;
104    }
105    let mut lines = [""; MAX_LINES];
106    for i in 0..count {
107        lines[i] = core::str::from_utf8(owned[i].as_bytes()).unwrap_or("<non-utf8>");
108    }
109    if count > 0 {
110        crate::arch::x86_64::vga::panic_draw_direct(&lines[..count]);
111    }
112}
113
114/// One render opportunity per timer tick, independent of commands/status I/O.
115pub extern "C" fn console_task_main() -> ! {
116    CONSOLE_TASK_ACTIVE.store(true, Ordering::Release);
117    let mut last_tick = u64::MAX;
118    loop {
119        let tick = crate::process::scheduler::ticks();
120        if tick != last_tick {
121            last_tick = tick;
122            if !crate::debug_cfg::is_quiet() {
123                vgabuf_flush_to_framebuffer();
124            }
125        }
126        crate::process::yield_task();
127    }
128}