From 88f08de3af80641194f8afbf47c98648e8db3e5a Mon Sep 17 00:00:00 2001 From: Qingsong Chen Date: Tue, 29 Apr 2025 08:45:08 +0000 Subject: [PATCH] Refactor framebuffer --- Cargo.lock | 2 + kernel/Cargo.toml | 1 + kernel/comps/framebuffer/Cargo.toml | 5 +- kernel/comps/framebuffer/src/console.rs | 212 ++++++++++++++++++++ kernel/comps/framebuffer/src/framebuffer.rs | 147 ++++++++++++++ kernel/comps/framebuffer/src/lib.rs | 209 +------------------ kernel/comps/framebuffer/src/pixel.rs | 104 ++++++++++ kernel/src/driver/mod.rs | 7 + kernel/src/lib.rs | 8 + tools/qemu_args.sh | 3 +- 10 files changed, 494 insertions(+), 204 deletions(-) create mode 100644 kernel/comps/framebuffer/src/console.rs create mode 100644 kernel/comps/framebuffer/src/framebuffer.rs create mode 100644 kernel/comps/framebuffer/src/pixel.rs diff --git a/Cargo.lock b/Cargo.lock index b6e2db626..1fce0a6cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,7 @@ dependencies = [ name = "aster-framebuffer" version = "0.1.0" dependencies = [ + "aster-console", "component", "font8x8", "log", @@ -184,6 +185,7 @@ dependencies = [ "aster-bigtcp", "aster-block", "aster-console", + "aster-framebuffer", "aster-input", "aster-logger", "aster-mlsdisk", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1f7107c2b..9ee1789fc 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -11,6 +11,7 @@ aster-input = { path = "comps/input" } aster-block = { path = "comps/block" } aster-network = { path = "comps/network" } aster-console = { path = "comps/console" } +aster-framebuffer = { path = "comps/framebuffer" } aster-softirq = { path = "comps/softirq" } aster-logger = { path = "comps/logger" } aster-mlsdisk = { path = "comps/mlsdisk" } diff --git a/kernel/comps/framebuffer/Cargo.toml b/kernel/comps/framebuffer/Cargo.toml index 3ccffef2b..078cc4310 100644 --- a/kernel/comps/framebuffer/Cargo.toml +++ b/kernel/comps/framebuffer/Cargo.toml @@ -8,11 +8,10 @@ edition = "2021" [dependencies] ostd = { path = "../../../ostd" } component = { path = "../../libs/comp-sys/component" } +aster-console = { path = "../console" } log = "0.4" spin = "0.9.4" -font8x8 = { version = "0.2.5", default-features = false, features = [ - "unicode", -] } +font8x8 = { version = "0.2.5", default-features = false, features = [ "unicode" ] } [lints] workspace = true diff --git a/kernel/comps/framebuffer/src/console.rs b/kernel/comps/framebuffer/src/console.rs new file mode 100644 index 000000000..5bc3a58e0 --- /dev/null +++ b/kernel/comps/framebuffer/src/console.rs @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{sync::Arc, vec::Vec}; + +use aster_console::{AnyConsoleDevice, ConsoleCallback}; +use font8x8::UnicodeFonts; +use ostd::{ + sync::{LocalIrqDisabled, SpinLock}, + Error, Result, +}; +use spin::Once; + +use crate::{FrameBuffer, Pixel, FRAMEBUFFER}; + +/// The font width in pixels when using `font8x8`. +const FONT_WIDTH: usize = 8; + +/// The font height in pixels when using `font8x8`. +const FONT_HEIGHT: usize = 8; + +/// A text console rendered onto the framebuffer. +#[derive(Debug)] +pub struct FramebufferConsole { + state: SpinLock, +} + +pub static CONSOLE_NAME: &str = "Framebuffer-Console"; + +pub static FRAMEBUFFER_CONSOLE: Once> = Once::new(); + +pub(crate) fn init() { + let Some(fb) = FRAMEBUFFER.get() else { + log::warn!("Framebuffer not initialized"); + return; + }; + + FRAMEBUFFER_CONSOLE.call_once(|| Arc::new(FramebufferConsole::new(fb.clone()))); +} + +impl AnyConsoleDevice for FramebufferConsole { + fn send(&self, buf: &[u8]) { + self.state.lock().send_buf(buf); + } + + fn register_callback(&self, _: &'static ConsoleCallback) { + // Unsupported, do nothing. + } +} + +impl FramebufferConsole { + /// Creates a new framebuffer console. + pub fn new(framebuffer: Arc) -> Self { + let bytes = alloc::vec![0u8; framebuffer.size()]; + Self { + state: SpinLock::new(ConsoleState { + enabled: true, + x_pos: 0, + y_pos: 0, + fg_color: Pixel::WHITE, + bg_color: Pixel::BLACK, + bytes, + backend: framebuffer, + }), + } + } + + /// Returns whether the console is enabled. + pub fn is_enabled(&self) -> bool { + self.state.lock().enabled + } + + /// Enables the console. + pub fn enable(&self) { + self.state.lock().enabled = true; + } + + /// Disables the console. + pub fn disable(&self) { + self.state.lock().enabled = false; + } + + /// Returns the current cursor position. + pub fn cursor(&self) -> (usize, usize) { + let state = self.state.lock(); + (state.x_pos, state.y_pos) + } + + /// Sets the cursor position. + pub fn set_cursor(&self, x: usize, y: usize) -> Result<()> { + let mut state = self.state.lock(); + if x > state.backend.width() - FONT_WIDTH || y > state.backend.height() - FONT_HEIGHT { + log::warn!("Invalid framebuffer cursor position: ({}, {})", x, y); + return Err(Error::InvalidArgs); + } + state.x_pos = x; + state.y_pos = y; + Ok(()) + } + + /// Returns the foreground color. + pub fn fg_color(&self) -> Pixel { + self.state.lock().fg_color + } + + /// Sets the foreground color. + pub fn set_fg_color(&self, val: Pixel) { + self.state.lock().fg_color = val; + } + + /// Returns the background color. + pub fn bg_color(&self) -> Pixel { + self.state.lock().bg_color + } + + /// Sets the background color. + pub fn set_bg_color(&self, val: Pixel) { + self.state.lock().bg_color = val; + } +} + +#[derive(Debug)] +struct ConsoleState { + // FIXME: maybe we should drop the whole `ConsoleState` when it's disabled. + enabled: bool, + x_pos: usize, + y_pos: usize, + fg_color: Pixel, + bg_color: Pixel, + bytes: Vec, + backend: Arc, +} + +impl ConsoleState { + fn carriage_return(&mut self) { + self.x_pos = 0; + } + + fn newline(&mut self) { + if self.y_pos >= self.backend.height() - FONT_HEIGHT { + self.shift_lines_up(); + } + self.y_pos += FONT_HEIGHT; + self.x_pos = 0; + } + + fn shift_lines_up(&mut self) { + let offset = self.backend.calc_offset(0, FONT_HEIGHT).as_usize(); + self.bytes.copy_within(offset.., 0); + self.bytes[self.backend.size() - offset..].fill(0); + self.backend.write_bytes_at(0, &self.bytes).unwrap(); + self.y_pos -= FONT_HEIGHT; + } + + /// Sends a single character to be drawn on the framebuffer. + fn send_char(&mut self, c: char) { + if c == '\n' { + self.newline(); + return; + } else if c == '\r' { + self.carriage_return(); + return; + } + + if self.x_pos + FONT_WIDTH > self.backend.width() { + self.newline(); + } + + let rendered = font8x8::BASIC_FONTS + .get(c) + .expect("character not found in basic font"); + let fg_pixel = self.backend.render_pixel(self.fg_color); + let bg_pixel = self.backend.render_pixel(self.bg_color); + let mut offset = self.backend.calc_offset(self.x_pos, self.y_pos); + for byte in rendered.iter() { + for bit in 0..8 { + let on = *byte & (1 << bit) != 0; + let pixel = if on { fg_pixel } else { bg_pixel }; + + // Cache the rendered pixel + self.bytes[offset.as_usize()..offset.as_usize() + pixel.nbytes()] + .copy_from_slice(pixel.as_slice()); + // Write the pixel to the framebuffer + self.backend.write_pixel_at(offset, pixel).unwrap(); + + offset.x_add(1); + } + offset.x_add(-(FONT_WIDTH as isize)); + offset.y_add(1); + } + self.x_pos += FONT_WIDTH; + } + + /// Sends a buffer of bytes to be drawn on the framebuffer. + /// + /// # Panics + /// + /// This method will panic if the buffer contains any characters + /// other than Basic Latin characters (`U+0000` - `U+007F`). + fn send_buf(&mut self, buf: &[u8]) { + if !self.enabled { + return; + } + + // TODO: handle ANSI escape sequences. + for &byte in buf.iter() { + if byte != 0 { + let char = char::from_u32(byte as u32).unwrap(); + self.send_char(char); + } + } + } +} diff --git a/kernel/comps/framebuffer/src/framebuffer.rs b/kernel/comps/framebuffer/src/framebuffer.rs new file mode 100644 index 000000000..4cc9f8aac --- /dev/null +++ b/kernel/comps/framebuffer/src/framebuffer.rs @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::sync::Arc; + +use ostd::{boot::boot_info, io::IoMem, mm::VmIo, Result}; +use spin::Once; + +use crate::{Pixel, PixelFormat, RenderedPixel}; + +/// The framebuffer used for text or graphical output. +/// +/// # Notes +/// +/// It is highly recommended to use a synchronization primitive, such as a `SpinLock`, to +/// lock the framebuffer before performing any operation on it. +/// Failing to properly synchronize access can result in corrupted framebuffer content +/// or unspecified behavior during rendering. +#[derive(Debug)] +pub struct FrameBuffer { + io_mem: IoMem, + width: usize, + height: usize, + pixel_format: PixelFormat, +} + +pub static FRAMEBUFFER: Once> = Once::new(); + +pub(crate) fn init() { + let Some(framebuffer_arg) = boot_info().framebuffer_arg else { + log::warn!("Framebuffer not found"); + return; + }; + + if framebuffer_arg.address == 0 { + log::error!("Framebuffer address is zero"); + return; + } + + // FIXME: There are several pixel formats that have the same BPP. We lost the information + // during the boot phase, so here we guess the pixel format on a best effort basis. + let pixel_format = match framebuffer_arg.bpp { + 8 => PixelFormat::Grayscale8, + 16 => PixelFormat::Rgb565, + 24 => PixelFormat::Rgb888, + 32 => PixelFormat::BgrReserved, + _ => { + log::error!( + "Unsupported framebuffer pixel format: {} bpp", + framebuffer_arg.bpp + ); + return; + } + }; + + let framebuffer = { + let fb_base = framebuffer_arg.address; + let fb_size = framebuffer_arg.width + * framebuffer_arg.height + * (framebuffer_arg.bpp / u8::BITS as usize); + let io_mem = IoMem::acquire(fb_base..fb_base + fb_size).unwrap(); + FrameBuffer { + io_mem, + width: framebuffer_arg.width, + height: framebuffer_arg.height, + pixel_format, + } + }; + + framebuffer.clear(); + FRAMEBUFFER.call_once(|| Arc::new(framebuffer)); +} + +impl FrameBuffer { + /// Returns the size of the framebuffer in bytes. + pub fn size(&self) -> usize { + self.io_mem.length() + } + + /// Returns the width of the framebuffer in pixels. + pub fn width(&self) -> usize { + self.width + } + + /// Returns the height of the framebuffer in pixels. + pub fn height(&self) -> usize { + self.height + } + + /// Returns the pixel format of the framebuffer. + pub fn pixel_format(&self) -> PixelFormat { + self.pixel_format + } + + /// Renders the pixel according to the pixel format of the framebuffer. + pub fn render_pixel(&self, pixel: Pixel) -> RenderedPixel { + pixel.render(self.pixel_format) + } + + /// Calculates the offset of a pixel at the specified position. + pub fn calc_offset(&self, x: usize, y: usize) -> PixelOffset { + PixelOffset { + fb: self, + offset: ((y * self.width + x) * self.pixel_format.nbytes()) as isize, + } + } + + /// Writes a pixel at the specified position. + pub fn write_pixel_at(&self, offset: PixelOffset, pixel: RenderedPixel) -> Result<()> { + self.io_mem.write_bytes(offset.as_usize(), pixel.as_slice()) + } + + /// Writes raw bytes at the specified offset. + pub fn write_bytes_at(&self, offset: usize, bytes: &[u8]) -> Result<()> { + self.io_mem.write_bytes(offset, bytes) + } + + /// Clears the framebuffer with default color (black). + pub fn clear(&self) { + let frame = alloc::vec![0u8; self.size()]; + self.write_bytes_at(0, &frame).unwrap(); + } +} + +/// The offset of a pixel in the framebuffer. +#[derive(Debug, Clone, Copy)] +pub struct PixelOffset<'a> { + fb: &'a FrameBuffer, + offset: isize, +} + +impl PixelOffset<'_> { + /// Adds the specified delta to the x coordinate. + pub fn x_add(&mut self, x_delta: isize) { + let delta = x_delta * self.fb.pixel_format.nbytes() as isize; + self.offset += delta; + } + + /// Adds the specified delta to the y coordinate. + pub fn y_add(&mut self, y_delta: isize) { + let delta = y_delta * (self.fb.width * self.fb.pixel_format.nbytes()) as isize; + self.offset += delta; + } + + pub fn as_usize(&self) -> usize { + self.offset as _ + } +} diff --git a/kernel/comps/framebuffer/src/lib.rs b/kernel/comps/framebuffer/src/lib.rs index 1a5f5408b..040ec2c0b 100644 --- a/kernel/comps/framebuffer/src/lib.rs +++ b/kernel/comps/framebuffer/src/lib.rs @@ -6,209 +6,18 @@ extern crate alloc; -use alloc::{vec, vec::Vec}; -use core::{ - fmt, - ops::{Index, IndexMut}, -}; +mod console; +mod framebuffer; +mod pixel; use component::{init_component, ComponentInitError}; -use font8x8::UnicodeFonts; -use ostd::{ - boot::{boot_info, memory_region::MemoryRegionType}, - io::IoMem, - mm::{VmIo, PAGE_SIZE}, - sync::SpinLock, -}; -use spin::Once; +pub use console::{FramebufferConsole, CONSOLE_NAME, FRAMEBUFFER_CONSOLE}; +pub use framebuffer::{FrameBuffer, FRAMEBUFFER}; +pub use pixel::{Pixel, PixelFormat, RenderedPixel}; #[init_component] -fn framebuffer_init() -> Result<(), ComponentInitError> { - init(); +fn init() -> Result<(), ComponentInitError> { + framebuffer::init(); + console::init(); Ok(()) } - -pub(crate) static WRITER: Once> = Once::new(); - -// ignore the warnings since we use the `todo!` macro. -#[expect(unused_variables)] -#[expect(unreachable_code)] -#[expect(clippy::diverging_sub_expression)] -pub(crate) fn init() { - let mut writer = { - let Some(framebuffer) = boot_info().framebuffer_arg else { - return; - }; - let mut size = 0; - for region in boot_info().memory_regions.iter() { - if region.typ() == MemoryRegionType::Framebuffer { - size = region.len(); - } - } - - let page_size = size / PAGE_SIZE; - - let start_paddr = framebuffer.address; - let io_mem = todo!("IoMem is private for components now, should fix it."); - - let mut buffer: Vec = vec![0; size]; - log::debug!("Found framebuffer:{:?}", framebuffer); - - Writer { - io_mem, - x_pos: 0, - y_pos: 0, - bytes_per_pixel: (framebuffer.bpp / 8), - width: framebuffer.width, - height: framebuffer.height, - buffer: buffer.leak(), - } - }; - writer.clear(); - - WRITER.call_once(|| SpinLock::new(writer)); -} - -pub(crate) struct Writer { - io_mem: IoMem, - /// FIXME: remove buffer. The meaning of buffer is to facilitate the various operations of framebuffer - buffer: &'static mut [u8], - - bytes_per_pixel: usize, - width: usize, - height: usize, - - x_pos: usize, - y_pos: usize, -} - -impl Writer { - fn newline(&mut self) { - self.y_pos += 8; - self.carriage_return(); - } - - fn carriage_return(&mut self) { - self.x_pos = 0; - } - - /// Erases all text on the screen - pub fn clear(&mut self) { - self.x_pos = 0; - self.y_pos = 0; - self.buffer.fill(0); - self.io_mem.write_bytes(0, self.buffer).unwrap(); - } - - /// Everything moves up one letter in size - fn shift_lines_up(&mut self) { - let offset = self.bytes_per_pixel * 8; - self.buffer.copy_within(offset.., 0); - self.io_mem.write_bytes(0, self.buffer).unwrap(); - self.y_pos -= 8; - } - - fn width(&self) -> usize { - self.width - } - - fn height(&self) -> usize { - self.height - } - - fn write_char(&mut self, c: char) { - match c { - '\n' => self.newline(), - '\r' => self.carriage_return(), - c => { - if self.x_pos >= self.width() { - self.newline(); - } - while self.y_pos >= (self.height() - 8) { - self.shift_lines_up(); - } - let rendered = font8x8::BASIC_FONTS - .get(c) - .expect("character not found in basic font"); - self.write_rendered_char(rendered); - } - } - } - - fn write_rendered_char(&mut self, rendered_char: [u8; 8]) { - for (y, byte) in rendered_char.iter().enumerate() { - for (x, bit) in (0..8).enumerate() { - let on = *byte & (1 << bit) != 0; - self.write_pixel(self.x_pos + x, self.y_pos + y, on); - } - } - self.x_pos += 8; - } - - fn write_pixel(&mut self, x: usize, y: usize, on: bool) { - let pixel_offset = y * self.width + x; - let color = if on { - [0x33, 0xff, 0x66, 0] - } else { - [0, 0, 0, 0] - }; - let bytes_per_pixel = self.bytes_per_pixel; - let byte_offset = pixel_offset * bytes_per_pixel; - self.buffer - .index_mut(byte_offset..(byte_offset + bytes_per_pixel)) - .copy_from_slice(&color[..bytes_per_pixel]); - self.io_mem - .write_bytes( - byte_offset, - self.buffer - .index(byte_offset..(byte_offset + bytes_per_pixel)), - ) - .unwrap(); - } - - /// Writes the given ASCII string to the buffer. - /// - /// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not** - /// support strings with non-ASCII characters, since they can't be printed in the VGA text - /// mode. - fn write_string(&mut self, s: &str) { - for char in s.chars() { - self.write_char(char); - } - } -} - -impl fmt::Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.write_string(s); - Ok(()) - } -} - -/// Like the `print!` macro in the standard library, but prints to the VGA text buffer. -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*))); -} - -/// Like the `println!` macro in the standard library, but prints to the VGA text buffer. -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); -} - -/// Prints the given formatted string to the VGA text buffer -/// through the global `WRITER` instance. -#[doc(hidden)] -pub fn _print(args: fmt::Arguments) { - use core::fmt::Write; - - WRITER - .get() - .unwrap() - .disable_irq() - .lock() - .write_fmt(args) - .unwrap(); -} diff --git a/kernel/comps/framebuffer/src/pixel.rs b/kernel/comps/framebuffer/src/pixel.rs new file mode 100644 index 000000000..a21854b25 --- /dev/null +++ b/kernel/comps/framebuffer/src/pixel.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MPL-2.0 + +/// Individual pixel data containing raw channel values. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Pixel { + pub red: u8, + pub green: u8, + pub blue: u8, +} + +/// Pixel format that defines the memory layout of each pixel in the framebuffer. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PixelFormat { + /// Each pixel uses 8 bits to represent its grayscale intensity, ranging from 0 (black) to 255 (white). + Grayscale8, + /// Each pixel uses 16 bits, with 5 bits for Red, 6 bits for Green, and 5 bits for Blue. + Rgb565, + /// Each pixel uses 24 bits, with 8 bits for Red, 8 bits for Green, and 8 bits for Blue. + Rgb888, + /// Each pixel uses 32 bits, with 8 bits for Blue, 8 bits for Green, 8 bits for Red, and 8 bits reserved. + BgrReserved, +} + +/// A rendered pixel in a specific format. +#[derive(Debug, Copy, Clone)] +pub struct RenderedPixel { + buf: [u8; 4], + len: u8, +} + +impl Pixel { + /// Renders the pixel into a specific format. + pub fn render(&self, format: PixelFormat) -> RenderedPixel { + let mut buf = [0; 4]; + match format { + PixelFormat::Grayscale8 => { + // Calculate the grayscale value + let red_weight = 77 * self.red as u16; // Equivalent to 0.299 * 256 + let green_weight = 150 * self.green as u16; // Equivalent to 0.587 * 256 + let blue_weight = 29 * self.blue as u16; // Equivalent to 0.114 * 256 + let grayscale = (red_weight + green_weight + blue_weight) >> 8; // Normalize to 0-255 + buf[0] = grayscale as u8; + RenderedPixel { buf, len: 1 } + } + PixelFormat::Rgb565 => { + let r = (self.red >> 3) as u16; // Red (5 bits) + let g = (self.green >> 2) as u16; // Green (6 bits) + let b = (self.blue >> 3) as u16; // Blue (5 bits) + let rgb565 = (r << 11) | (g << 5) | b; // Combine into RGB565 format + buf[0..2].copy_from_slice(&rgb565.to_be_bytes()); + RenderedPixel { buf, len: 2 } + } + PixelFormat::Rgb888 => { + buf[0] = self.red; + buf[1] = self.green; + buf[2] = self.blue; + RenderedPixel { buf, len: 3 } + } + PixelFormat::BgrReserved => { + buf[0] = self.blue; + buf[1] = self.green; + buf[2] = self.red; + RenderedPixel { buf, len: 4 } + } + } + } +} + +impl PixelFormat { + /// Returns the number of bytes per pixel (color depth). + pub fn nbytes(&self) -> usize { + match self { + PixelFormat::Grayscale8 => 1, + PixelFormat::Rgb565 => 2, + PixelFormat::Rgb888 => 3, + PixelFormat::BgrReserved => 4, + } + } +} + +impl RenderedPixel { + /// Returns the number of bytes in the rendered pixel. + pub fn nbytes(&self) -> usize { + self.len as usize + } + + /// Returns a slice to the rendered pixel data. + pub fn as_slice(&self) -> &[u8] { + &self.buf[..self.nbytes()] + } +} + +impl Pixel { + pub const WHITE: Pixel = Pixel { + red: 0xFF, + green: 0xFF, + blue: 0xFF, + }; + pub const BLACK: Pixel = Pixel { + red: 0x00, + green: 0x00, + blue: 0x00, + }; +} diff --git a/kernel/src/driver/mod.rs b/kernel/src/driver/mod.rs index 750803b5e..6c381867f 100644 --- a/kernel/src/driver/mod.rs +++ b/kernel/src/driver/mod.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 +use alloc::string::ToString; + +use aster_framebuffer::{CONSOLE_NAME, FRAMEBUFFER_CONSOLE}; use log::info; pub fn init() { @@ -7,4 +10,8 @@ pub fn init() { for (name, _) in aster_input::all_devices() { info!("Found Input device, name:{}", name); } + + if let Some(console) = FRAMEBUFFER_CONSOLE.get() { + aster_console::register_device(CONSOLE_NAME.to_string(), console.clone()); + } } diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 8a99f5d4f..b318a660a 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -32,6 +32,7 @@ #![feature(associated_type_defaults)] #![register_tool(component_access_control)] +use aster_framebuffer::FRAMEBUFFER_CONSOLE; use kcmdline::KCmdlineArg; use ostd::{ arch::qemu::{exit_qemu, QemuExitCode}, @@ -145,6 +146,13 @@ fn init_thread() { print_banner(); + // FIXME: CI fails due to suspected performance issues with the framebuffer console. + // Additionally, userspace program may render GUIs using the framebuffer, + // so we disable the framebuffer console here. + if let Some(console) = FRAMEBUFFER_CONSOLE.get() { + console.disable(); + }; + let karg: KCmdlineArg = boot_info().kernel_cmdline.as_str().into(); let initproc = Process::spawn_user_process( diff --git a/tools/qemu_args.sh b/tools/qemu_args.sh index 2e54dcb33..9be9809ed 100755 --- a/tools/qemu_args.sh +++ b/tools/qemu_args.sh @@ -12,6 +12,7 @@ # - VSOCK: "off" or "on"; # - SMP: number of CPUs; # - MEM: amount of memory, e.g. "8G". +# - VNC_PORT: VNC port, default is "42". OVMF=${OVMF:-"on"} VHOST=${VHOST:-"off"} @@ -78,7 +79,7 @@ COMMON_QEMU_ARGS="\ -m ${MEM:-8G} \ --no-reboot \ -nographic \ - -display none \ + -display vnc=0.0.0.0:${VNC_PORT:-42} \ -serial chardev:mux \ -monitor chardev:mux \ -chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \