Skip to content

Commit 1c3f145

Browse files
authored
Merge pull request #206 from ryanbreen/feat/block-io-confetti-pointer
feat: block I/O fix, confetti demo, and mouse pointer support
2 parents ee5e80a + fa105b3 commit 1c3f145

File tree

11 files changed

+796
-619
lines changed

11 files changed

+796
-619
lines changed

kernel/src/arch_impl/aarch64/syscall_entry.rs

Lines changed: 196 additions & 615 deletions
Large diffs are not rendered by default.

kernel/src/drivers/virtio/block_mmio.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
use super::mmio::{VirtioMmioDevice, device_id, VIRTIO_MMIO_BASE, VIRTIO_MMIO_SIZE, VIRTIO_MMIO_COUNT};
77
use core::ptr::read_volatile;
88
use core::sync::atomic::{fence, Ordering};
9+
use spin::Mutex;
10+
11+
/// Lock protecting all shared DMA buffers (REQ_HEADER, DATA_BUF, STATUS_BUF, QUEUE_MEM).
12+
///
13+
/// The x86_64 PCI block driver uses Mutex<Virtqueue> for the same purpose (block.rs:81).
14+
/// Without this lock, concurrent exec() calls (e.g., init spawning telnetd + bsh) corrupt
15+
/// each other's request headers mid-flight, causing ELF load failures.
16+
static BLOCK_IO_LOCK: Mutex<()> = Mutex::new(());
917

1018
// Import dsb_sy from the shared CPU module to avoid duplication
1119
use crate::arch_impl::aarch64::cpu::dsb_sy;
@@ -267,8 +275,36 @@ fn init_device(device: &mut VirtioMmioDevice, base: u64) -> Result<(), &'static
267275
Ok(())
268276
}
269277

270-
/// Read a sector from the block device
278+
/// Read a sector from the block device.
279+
///
280+
/// Serializes access with BLOCK_IO_LOCK to prevent concurrent DMA buffer corruption.
281+
/// Disables interrupts before acquiring the lock to prevent same-core deadlock
282+
/// (matches the pattern in `kernel/src/process/mod.rs:85-88`).
271283
pub fn read_sector(sector: u64, buffer: &mut [u8; SECTOR_SIZE]) -> Result<(), &'static str> {
284+
// Save DAIF and disable interrupts to prevent same-core deadlock:
285+
// if a timer interrupt preempts while we hold the spinlock, the scheduler
286+
// could switch to another thread on this core that tries to acquire the
287+
// same lock, spinning forever.
288+
let saved_daif: u64;
289+
unsafe {
290+
core::arch::asm!("mrs {}, daif", out(reg) saved_daif, options(nomem, nostack));
291+
core::arch::asm!("msr daifset, #0xf", options(nomem, nostack));
292+
}
293+
294+
let _guard = BLOCK_IO_LOCK.lock();
295+
let result = read_sector_inner(sector, buffer);
296+
drop(_guard);
297+
298+
// Restore interrupt state
299+
unsafe {
300+
core::arch::asm!("msr daif, {}", in(reg) saved_daif, options(nomem, nostack));
301+
}
302+
303+
result
304+
}
305+
306+
/// Inner implementation of read_sector, called with BLOCK_IO_LOCK held.
307+
fn read_sector_inner(sector: u64, buffer: &mut [u8; SECTOR_SIZE]) -> Result<(), &'static str> {
272308
// Use raw pointers to avoid references to mutable statics
273309
let state = unsafe {
274310
let ptr = &raw mut BLOCK_DEVICE;
@@ -390,8 +426,29 @@ pub fn read_sector(sector: u64, buffer: &mut [u8; SECTOR_SIZE]) -> Result<(), &'
390426
Ok(())
391427
}
392428

393-
/// Write a sector to the block device
429+
/// Write a sector to the block device.
430+
///
431+
/// Serializes access with BLOCK_IO_LOCK (same as read_sector).
394432
pub fn write_sector(sector: u64, buffer: &[u8; SECTOR_SIZE]) -> Result<(), &'static str> {
433+
let saved_daif: u64;
434+
unsafe {
435+
core::arch::asm!("mrs {}, daif", out(reg) saved_daif, options(nomem, nostack));
436+
core::arch::asm!("msr daifset, #0xf", options(nomem, nostack));
437+
}
438+
439+
let _guard = BLOCK_IO_LOCK.lock();
440+
let result = write_sector_inner(sector, buffer);
441+
drop(_guard);
442+
443+
unsafe {
444+
core::arch::asm!("msr daif, {}", in(reg) saved_daif, options(nomem, nostack));
445+
}
446+
447+
result
448+
}
449+
450+
/// Inner implementation of write_sector, called with BLOCK_IO_LOCK held.
451+
fn write_sector_inner(sector: u64, buffer: &[u8; SECTOR_SIZE]) -> Result<(), &'static str> {
395452
// Use raw pointers to avoid references to mutable statics
396453
let state = unsafe {
397454
let ptr = &raw mut BLOCK_DEVICE;

kernel/src/graphics/terminal_manager.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::primitives::{draw_text, fill_rect, Canvas, Color, Rect, TextStyle};
88
use super::terminal::TerminalPane;
99
use alloc::collections::VecDeque;
1010
use alloc::string::String;
11+
use alloc::vec::Vec;
1112
use spin::Mutex;
1213

1314
// Architecture-specific framebuffer imports
@@ -103,6 +104,10 @@ pub struct TerminalManager {
103104
has_unread: [bool; 2],
104105
/// Scroll offset for Logs tab (0 = following tail, >0 = scrolled up)
105106
logs_scroll_offset: usize,
107+
/// Saved pixel data for the shell terminal area (saved when switching away)
108+
shell_pixel_backup: Option<Vec<u8>>,
109+
/// Saved cursor position for the shell terminal (col, row)
110+
shell_cursor_backup: (usize, usize),
106111
}
107112

108113
impl TerminalManager {
@@ -136,6 +141,8 @@ impl TerminalManager {
136141
tab_shortcuts: ["F1", "F2"],
137142
has_unread: [false, false],
138143
logs_scroll_offset: 0,
144+
shell_pixel_backup: None,
145+
shell_cursor_backup: (0, 0),
139146
}
140147
}
141148

@@ -155,6 +162,12 @@ impl TerminalManager {
155162
// Hide cursor
156163
self.terminal_pane.draw_cursor(canvas, false);
157164

165+
// If leaving the Shell tab, save its pixel content and cursor position
166+
if self.active_idx == TerminalId::Shell as usize {
167+
self.shell_cursor_backup = self.terminal_pane.cursor();
168+
self.shell_pixel_backup = Some(self.save_terminal_pixels(canvas));
169+
}
170+
158171
self.active_idx = new_idx;
159172
self.has_unread[new_idx] = false;
160173

@@ -165,8 +178,12 @@ impl TerminalManager {
165178
// Restore content for the new active terminal
166179
match id {
167180
TerminalId::Shell => {
168-
// Shell: just show prompt (shell will redraw its state)
169-
self.terminal_pane.write_str(canvas, "breenix> ");
181+
// Restore the saved shell pixel content
182+
if let Some(ref saved) = self.shell_pixel_backup {
183+
self.restore_terminal_pixels(canvas, saved);
184+
}
185+
let (col, row) = self.shell_cursor_backup;
186+
self.terminal_pane.set_cursor(col, row);
170187
}
171188
TerminalId::Logs => {
172189
// Reset scroll to follow tail when switching to Logs
@@ -198,6 +215,46 @@ impl TerminalManager {
198215
self.terminal_pane.set_cursor(0, 0);
199216
}
200217

218+
/// Save the terminal content area pixels to a Vec.
219+
fn save_terminal_pixels(&self, canvas: &impl Canvas) -> Vec<u8> {
220+
let pane_y = self.region_y + TAB_HEIGHT + 2;
221+
let pane_height = self.region_height.saturating_sub(TAB_HEIGHT + 2);
222+
let bpp = canvas.bytes_per_pixel();
223+
let stride = canvas.stride();
224+
let buffer = canvas.buffer();
225+
let row_bytes = self.region_width * bpp;
226+
227+
let mut saved = Vec::with_capacity(row_bytes * pane_height);
228+
for row in 0..pane_height {
229+
let offset = (pane_y + row) * stride * bpp + self.region_x * bpp;
230+
if offset + row_bytes <= buffer.len() {
231+
saved.extend_from_slice(&buffer[offset..offset + row_bytes]);
232+
}
233+
}
234+
saved
235+
}
236+
237+
/// Restore previously saved terminal content area pixels.
238+
fn restore_terminal_pixels(&self, canvas: &mut impl Canvas, saved: &[u8]) {
239+
let pane_y = self.region_y + TAB_HEIGHT + 2;
240+
let pane_height = self.region_height.saturating_sub(TAB_HEIGHT + 2);
241+
let bpp = canvas.bytes_per_pixel();
242+
let stride = canvas.stride();
243+
let row_bytes = self.region_width * bpp;
244+
245+
let buffer = canvas.buffer_mut();
246+
let mut src_offset = 0;
247+
for row in 0..pane_height {
248+
let dst_offset = (pane_y + row) * stride * bpp + self.region_x * bpp;
249+
if dst_offset + row_bytes <= buffer.len() && src_offset + row_bytes <= saved.len() {
250+
buffer[dst_offset..dst_offset + row_bytes]
251+
.copy_from_slice(&saved[src_offset..src_offset + row_bytes]);
252+
}
253+
src_offset += row_bytes;
254+
}
255+
canvas.mark_dirty_region(self.region_x, pane_y, self.region_width, pane_height);
256+
}
257+
201258
/// Initialize the terminal manager.
202259
pub fn init(&mut self, canvas: &mut impl Canvas) {
203260
// Clear entire region

kernel/src/syscall/dispatcher.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub fn dispatch_syscall(
119119
SyscallNumber::FbInfo => super::graphics::sys_fbinfo(arg1),
120120
SyscallNumber::FbDraw => super::graphics::sys_fbdraw(arg1),
121121
SyscallNumber::FbMmap => super::graphics::sys_fbmmap(),
122+
SyscallNumber::GetMousePos => super::graphics::sys_get_mouse_pos(arg1),
122123
// Testing/diagnostic syscalls (Breenix-specific)
123124
SyscallNumber::CowStats => super::handlers::sys_cow_stats(arg1),
124125
SyscallNumber::SimulateOom => super::handlers::sys_simulate_oom(arg1),

kernel/src/syscall/graphics.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,41 @@ pub fn sys_fbdraw(_cmd_ptr: u64) -> SyscallResult {
448448
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
449449
}
450450

451+
/// sys_get_mouse_pos - Get current mouse cursor position
452+
///
453+
/// # Arguments
454+
/// * `out_ptr` - Pointer to a [u32; 2] array in userspace: [x, y]
455+
///
456+
/// # Returns
457+
/// * 0 on success
458+
/// * -EFAULT if out_ptr is invalid
459+
#[cfg(target_arch = "aarch64")]
460+
pub fn sys_get_mouse_pos(out_ptr: u64) -> SyscallResult {
461+
if out_ptr == 0 || out_ptr >= USER_SPACE_MAX {
462+
return SyscallResult::Err(super::ErrorCode::Fault as u64);
463+
}
464+
465+
let end_ptr = out_ptr.saturating_add(8); // 2 * u32
466+
if end_ptr > USER_SPACE_MAX {
467+
return SyscallResult::Err(super::ErrorCode::Fault as u64);
468+
}
469+
470+
let (mx, my) = crate::drivers::virtio::input_mmio::mouse_position();
471+
472+
unsafe {
473+
let out = out_ptr as *mut [u32; 2];
474+
core::ptr::write(out, [mx, my]);
475+
}
476+
477+
SyscallResult::Ok(0)
478+
}
479+
480+
/// sys_get_mouse_pos - Stub for non-aarch64
481+
#[cfg(not(target_arch = "aarch64"))]
482+
pub fn sys_get_mouse_pos(_out_ptr: u64) -> SyscallResult {
483+
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
484+
}
485+
451486
/// sys_fbmmap - Map a framebuffer buffer into the calling process's address space
452487
///
453488
/// Allocates physical frames, maps them into the process as a compact left-pane

kernel/src/syscall/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ pub enum SyscallNumber {
141141
FbInfo = 410, // Breenix: get framebuffer info
142142
FbDraw = 411, // Breenix: draw to framebuffer (left pane)
143143
FbMmap = 412, // Breenix: mmap framebuffer into userspace
144+
GetMousePos = 413, // Breenix: get mouse cursor position
144145
CowStats = 500, // Breenix: get Copy-on-Write statistics (for testing)
145146
SimulateOom = 501, // Breenix: enable/disable OOM simulation (for testing)
146147
}
@@ -234,6 +235,7 @@ impl SyscallNumber {
234235
410 => Some(Self::FbInfo),
235236
411 => Some(Self::FbDraw),
236237
412 => Some(Self::FbMmap),
238+
413 => Some(Self::GetMousePos),
237239
500 => Some(Self::CowStats),
238240
501 => Some(Self::SimulateOom),
239241
_ => None,

libs/libbreenix/src/graphics.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,18 @@ pub fn fb_flush_rect(x: i32, y: i32, w: i32, h: i32) -> Result<(), Error> {
238238
fbdraw(&cmd)
239239
}
240240

241+
/// Get the current mouse cursor position.
242+
///
243+
/// # Returns
244+
/// * Ok((x, y)) - Mouse position in screen coordinates
245+
/// * Err(Error) - Error (ENODEV if no pointer device)
246+
pub fn mouse_pos() -> Result<(u32, u32), Error> {
247+
let mut pos: [u32; 2] = [0, 0];
248+
let ret = unsafe { raw::syscall1(nr::GET_MOUSE_POS, &mut pos as *mut [u32; 2] as u64) as i64 };
249+
Error::from_syscall(ret)?;
250+
Ok((pos[0], pos[1]))
251+
}
252+
241253
// ============================================================================
242254
// RAII Framebuffer Wrapper
243255
// ============================================================================

libs/libbreenix/src/syscall.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ pub mod nr {
9191
pub const FBINFO: u64 = 410; // Breenix: get framebuffer info
9292
pub const FBDRAW: u64 = 411; // Breenix: draw to framebuffer
9393
pub const FBMMAP: u64 = 412; // Breenix: mmap framebuffer into userspace
94+
pub const GET_MOUSE_POS: u64 = 413; // Breenix: get mouse cursor position
9495
pub const CLONE: u64 = 56; // Linux x86_64 clone
9596
pub const FUTEX: u64 = 202; // Linux x86_64 futex
9697
pub const GETRANDOM: u64 = 318; // Linux x86_64 getrandom

userspace/programs/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ path = "src/wc_test.rs"
506506
name = "which_test"
507507
path = "src/which_test.rs"
508508

509+
[[bin]]
510+
name = "confetti"
511+
path = "src/confetti.rs"
512+
509513
[[bin]]
510514
name = "bsh"
511515
path = "src/bsh.rs"

userspace/programs/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ STD_BINARIES=(
211211
"demo"
212212
"bounce"
213213
"particles"
214+
"confetti"
214215
"http_test"
215216
"register_init_test"
216217

0 commit comments

Comments
 (0)