Revises console (#946)

This commit is contained in:
Putta Khunchalee 2024-08-24 20:03:14 +07:00 committed by GitHub
parent c44798de47
commit 0f85fbb946
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 239 additions and 145 deletions

View File

@ -34,7 +34,7 @@ jobs:
- name: Install Flatpak runtimes
run: |
flatpak install --noninteractive flathub \
org.kde.Platform//6.6 org.kde.Sdk//6.6 \
org.kde.Platform//6.7 org.kde.Sdk//6.7 \
org.freedesktop.Sdk.Extension.rust-stable//23.08
if: ${{ steps.flatpak-runtime.outputs.cache-hit != 'true' }}
- name: Restore build files

View File

@ -21,9 +21,9 @@ jobs:
name: ${{ inputs.name }}
runs-on: ${{ inputs.macos }}
env:
CMAKE_PREFIX_PATH: qt/6.6.0/macos
QT_URL_BASE: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_660/qt.qt6.660.clang_64/6.6.0-0-202310040910qtbase-MacOS-MacOS_12-Clang-MacOS-MacOS_12-X86_64-ARM64.7z
QT_URL_SVG: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_660/qt.qt6.660.clang_64/6.6.0-0-202310040910qtsvg-MacOS-MacOS_12-Clang-MacOS-MacOS_12-X86_64-ARM64.7z
CMAKE_PREFIX_PATH: qt/6.7.2/macos
QT_URL_BASE: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_672/qt.qt6.672.clang_64/6.7.2-0-202406110330qtbase-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z
QT_URL_SVG: https://download.qt.io/online/qtsdkrepository/mac_x64/desktop/qt6_672/qt.qt6.672.clang_64/6.7.2-0-202406110330qtsvg-MacOS-MacOS_13-Clang-MacOS-MacOS_13-X86_64-ARM64.7z
steps:
- name: Checkout source
uses: actions/checkout@v4

View File

@ -1,7 +1,7 @@
app-id: io.github.obhq.Obliteration
default-branch: stable
runtime: org.kde.Platform
runtime-version: '6.6'
runtime-version: '6.7'
platform-extensions:
- org.freedesktop.Platform.GL.default
sdk: org.kde.Sdk

View File

@ -13,6 +13,13 @@
struct Param;
struct Pkg;
/**
* Log category.
*/
enum VmmLog {
VmmLog_Info,
};
/**
* Error object managed by Rust side.
*/
@ -41,6 +48,26 @@ struct VmmScreen {
;
};
/**
* Contains VMM event information.
*/
enum VmmEvent_Tag {
VmmEvent_Log,
};
struct VmmEvent_Log_Body {
enum VmmLog ty;
const char *data;
size_t len;
};
struct VmmEvent {
enum VmmEvent_Tag tag;
union {
struct VmmEvent_Log_Body log;
};
};
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
@ -89,12 +116,14 @@ struct RustError *update_firmware(const char *root,
void vmm_free(struct Vmm *vmm);
struct Vmm *vmm_run(const char *kernel, const struct VmmScreen *screen, struct RustError **err);
struct Vmm *vmm_run(const char *kernel,
const struct VmmScreen *screen,
bool (*event)(const struct VmmEvent*, void*),
void *cx,
struct RustError **err);
struct RustError *vmm_draw(struct Vmm *vmm);
void vmm_logs(const struct Vmm *vmm, void *cx, void (*cb)(uint8_t, const char*, size_t, void*));
#if defined(__linux__)
extern int kvm_check_version(int kvm, bool *compat);
#endif

View File

@ -40,6 +40,7 @@ fn main() {
conf.export
.rename
.insert("KvmTranslation".into(), "kvm_translation".into());
conf.enumeration.prefix_with_name = true;
conf.defines
.insert("target_os = linux".into(), "__linux__".into());
conf.defines

View File

@ -1,7 +1,8 @@
use super::{Console, Log};
use super::Console;
use crate::vmm::hv::{CpuIo, IoBuf};
use crate::vmm::hw::{DeviceContext, Ram};
use obvirt::console::{Commit, Memory};
use crate::vmm::VmmEvent;
use obvirt::console::{Memory, MsgType};
use std::error::Error;
use std::mem::offset_of;
use thiserror::Error;
@ -10,8 +11,6 @@ use thiserror::Error;
pub struct Context<'a> {
dev: &'a Console,
ram: &'a Ram,
file_len: usize,
file: String,
msg_len: usize,
msg: String,
}
@ -21,14 +20,12 @@ impl<'a> Context<'a> {
Self {
dev,
ram,
file_len: 0,
file: String::new(),
msg_len: 0,
msg: String::new(),
}
}
fn read_u32(&self, off: usize, exit: &mut dyn CpuIo) -> Result<u32, ExecError> {
fn read_u8(&self, off: usize, exit: &mut dyn CpuIo) -> Result<u8, ExecError> {
// Get data.
let data = match exit.buffer() {
IoBuf::Write(v) => v,
@ -36,9 +33,11 @@ impl<'a> Context<'a> {
};
// Parse data.
data.try_into()
.map(|v| u32::from_ne_bytes(v))
.map_err(|_| ExecError::InvalidData(off))
if data.len() != 1 {
Err(ExecError::InvalidData(off))
} else {
Ok(data[0])
}
}
fn read_usize(&self, off: usize, exit: &mut dyn CpuIo) -> Result<usize, ExecError> {
@ -84,7 +83,7 @@ impl<'a> Context<'a> {
}
impl<'a> DeviceContext for Context<'a> {
fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<(), Box<dyn Error>> {
fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<bool, Box<dyn Error>> {
// Check field.
let off = exit.addr() - self.dev.addr;
@ -92,36 +91,34 @@ impl<'a> DeviceContext for Context<'a> {
self.msg_len = self.read_usize(off, exit)?;
} else if off == offset_of!(Memory, msg_addr) {
self.msg.push_str(self.read_str(off, exit, self.msg_len)?);
} else if off == offset_of!(Memory, file_len) {
self.file_len = self.read_usize(off, exit)?;
} else if off == offset_of!(Memory, file_addr) {
self.file = self.read_str(off, exit, self.file_len)?.to_owned();
} else if off == offset_of!(Memory, commit) {
// Parse data.
let commit = self.read_u32(off, exit)?;
let (ty, line) = match Commit::parse(commit) {
let commit = self.read_u8(off, exit)?;
let ty = match MsgType::from_u8(commit) {
Some(v) => v,
None => return Err(Box::new(ExecError::InvalidCommit(commit))),
};
// Push log.
let mut logs = self.dev.logs.lock().unwrap();
// Trigger event.
let msg = std::mem::take(&mut self.msg);
let status = match ty {
MsgType::Info => unsafe {
self.dev.event.invoke(VmmEvent::Log {
ty: ty.into(),
data: msg.as_ptr().cast(),
len: msg.len(),
})
},
};
logs.push_back(Log {
ty,
file: std::mem::take(&mut self.file),
line,
msg: std::mem::take(&mut self.msg),
});
while logs.len() > 10000 {
logs.pop_front();
if !status {
return Ok(false);
}
} else {
return Err(Box::new(ExecError::UnknownField(off)));
}
Ok(())
Ok(true)
}
}
@ -141,5 +138,5 @@ enum ExecError {
TranslateVaddrFailed(usize, #[source] Box<dyn Error>),
#[error("{0:#} is not a valid commit")]
InvalidCommit(u32),
InvalidCommit(u8),
}

View File

@ -1,9 +1,8 @@
use self::context::Context;
use super::{Device, DeviceContext, Ram};
use obvirt::console::{Memory, MsgType};
use std::collections::VecDeque;
use crate::vmm::VmmEventHandler;
use obvirt::console::Memory;
use std::num::NonZero;
use std::sync::Mutex;
mod context;
@ -11,11 +10,11 @@ mod context;
pub struct Console {
addr: usize,
len: NonZero<usize>,
logs: Mutex<VecDeque<Log>>,
event: VmmEventHandler,
}
impl Console {
pub fn new(addr: usize, vm_page_size: NonZero<usize>) -> Self {
pub fn new(addr: usize, vm_page_size: NonZero<usize>, event: VmmEventHandler) -> Self {
let len = size_of::<Memory>()
.checked_next_multiple_of(vm_page_size.get())
.and_then(NonZero::new)
@ -23,11 +22,7 @@ impl Console {
addr.checked_add(len.get()).unwrap();
Self {
addr,
len,
logs: Mutex::default(),
}
Self { addr, len, event }
}
}
@ -44,11 +39,3 @@ impl Device for Console {
Box::new(Context::new(self, ram))
}
}
/// Contains data for each logging entry.
struct Log {
ty: MsgType,
file: String,
line: u32,
msg: String,
}

View File

@ -1,3 +1,4 @@
use super::VmmEventHandler;
use crate::vmm::hv::CpuIo;
use std::collections::BTreeMap;
use std::error::Error;
@ -10,12 +11,16 @@ pub use self::ram::*;
mod console;
mod ram;
pub fn setup_devices(start_addr: usize, vm_page_size: NonZero<usize>) -> DeviceTree {
pub fn setup_devices(
start_addr: usize,
vm_page_size: NonZero<usize>,
event: VmmEventHandler,
) -> DeviceTree {
let mut map = BTreeMap::<usize, Arc<dyn Device>>::new();
// Console.
let addr = start_addr;
let console = Arc::new(Console::new(addr, vm_page_size));
let console = Arc::new(Console::new(addr, vm_page_size, event));
assert!(map.insert(console.addr(), console.clone()).is_none());
@ -60,5 +65,5 @@ pub trait Device: Send + Sync {
/// Context to execute memory-mapped I/O operations on a virtual device.
pub trait DeviceContext {
fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<(), Box<dyn Error>>;
fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<bool, Box<dyn Error>>;
}

View File

@ -5,16 +5,15 @@ use self::screen::Screen;
use crate::error::RustError;
use obconf::{BootEnv, Vm};
use obvirt::console::MsgType;
use std::collections::{BTreeMap, VecDeque};
use std::collections::BTreeMap;
use std::error::Error;
use std::ffi::{c_char, c_void, CStr};
use std::io::Read;
use std::num::NonZero;
use std::ops::Deref;
use std::ptr::null_mut;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::thread::JoinHandle;
use thiserror::Error;
@ -32,6 +31,8 @@ pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) {
pub unsafe extern "C" fn vmm_run(
kernel: *const c_char,
screen: *const VmmScreen,
event: unsafe extern "C" fn(*const VmmEvent, *mut c_void) -> bool,
cx: *mut c_void,
err: *mut *mut RustError,
) -> *mut Vmm {
// Check if path UTF-8.
@ -359,7 +360,8 @@ pub unsafe extern "C" fn vmm_run(
}
// Allocate arguments.
let devices = Arc::new(setup_devices(Ram::SIZE, vm_page_size));
let event = VmmEventHandler { fp: event, cx };
let devices = Arc::new(setup_devices(Ram::SIZE, vm_page_size, event));
let env = BootEnv::Vm(Vm {
console: devices.console().addr(),
});
@ -398,7 +400,6 @@ pub unsafe extern "C" fn vmm_run(
};
// Setup arguments for main CPU.
let logs = Arc::new(Mutex::new(VecDeque::new()));
let shutdown = Arc::new(AtomicBool::new(false));
let args = CpuArgs {
hv,
@ -440,7 +441,6 @@ pub unsafe extern "C" fn vmm_run(
let vmm = Vmm {
cpus: vec![main],
screen,
logs,
shutdown,
};
@ -455,19 +455,6 @@ pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
}
}
#[no_mangle]
pub unsafe extern "C" fn vmm_logs(
vmm: *const Vmm,
cx: *mut c_void,
cb: unsafe extern "C" fn(u8, *const c_char, usize, *mut c_void),
) {
let logs = (*vmm).logs.lock().unwrap();
for (ty, msg) in logs.deref() {
cb(*ty as u8, msg.as_ptr().cast(), msg.len(), cx);
}
}
fn main_cpu(args: &CpuArgs, entry: usize, map: RamMap, status: Sender<Result<(), MainCpuError>>) {
// Create vCPU.
let mut cpu = match args.hv.create_cpu(0) {
@ -616,7 +603,13 @@ fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) {
// Check if I/O.
match exit.into_io() {
Ok(io) => match exec_io(&mut devices, io) {
Ok(_) => continue,
Ok(status) => {
if !status {
args.shutdown.store(true, Ordering::Relaxed);
}
continue;
}
Err(_) => todo!(),
},
Err(_) => todo!(),
@ -627,7 +620,7 @@ fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) {
fn exec_io<'a>(
devices: &mut BTreeMap<usize, (Box<dyn DeviceContext + 'a>, NonZero<usize>)>,
mut io: impl CpuIo,
) -> Result<(), Box<dyn Error>> {
) -> Result<bool, Box<dyn Error>> {
// Get target device.
let addr = io.addr();
let (_, (dev, end)) = devices.range_mut(..=addr).last().unwrap();
@ -663,7 +656,6 @@ fn get_page_size() -> Result<NonZero<usize>, std::io::Error> {
pub struct Vmm {
cpus: Vec<JoinHandle<()>>,
screen: self::screen::Default,
logs: Arc<Mutex<VecDeque<(MsgType, String)>>>,
shutdown: Arc<AtomicBool>,
}
@ -689,6 +681,48 @@ pub struct VmmScreen {
pub view: usize,
}
/// Encapsulates a function to handle VMM events.
#[derive(Clone, Copy)]
struct VmmEventHandler {
fp: unsafe extern "C" fn(*const VmmEvent, *mut c_void) -> bool,
cx: *mut c_void,
}
impl VmmEventHandler {
unsafe fn invoke(self, e: VmmEvent) -> bool {
(self.fp)(&e, self.cx)
}
}
unsafe impl Send for VmmEventHandler {}
unsafe impl Sync for VmmEventHandler {}
/// Contains VMM event information.
#[repr(C)]
#[allow(dead_code)] // TODO: Figure out why Rust think fields in each enum are not used.
pub enum VmmEvent {
Log {
ty: VmmLog,
data: *const c_char,
len: usize,
},
}
/// Log category.
#[repr(C)]
#[derive(Clone, Copy)]
pub enum VmmLog {
Info,
}
impl From<MsgType> for VmmLog {
fn from(value: MsgType) -> Self {
match value {
MsgType::Info => Self::Info,
}
}
}
/// Encapsulates arguments for a function to run a CPU.
struct CpuArgs {
hv: Arc<self::hv::Default>,

View File

@ -1,4 +1,3 @@
#include "core.hpp"
#include "initialize_wizard.hpp"
#include "main_window.hpp"
#include "settings.hpp"
@ -10,6 +9,7 @@
#include <QApplication>
#include <QMessageBox>
#include <QMetaObject>
#include <QThread>
#ifndef __APPLE__
#include <QVersionNumber>
#include <QVulkanInstance>
@ -27,14 +27,19 @@ static void panicHook(
size_t mlen,
void *cx)
{
QMetaObject::invokeMethod(reinterpret_cast<QObject *>(cx), [=]() {
auto main = reinterpret_cast<QObject *>(cx);
auto type = QThread::currentThread() == main->thread()
? Qt::DirectConnection
: Qt::BlockingQueuedConnection;
QMetaObject::invokeMethod(main, [=]() {
auto text = QString("An unexpected error occurred at %1:%2: %3")
.arg(QString::fromUtf8(file, flen))
.arg(line)
.arg(QString::fromUtf8(msg, mlen));
QMessageBox::critical(nullptr, "Fatal Error", text);
});
}, type);
}
int main(int argc, char *argv[])

View File

@ -30,6 +30,7 @@
#endif
#include <filesystem>
#include <iostream>
#include <utility>
#include <string.h>
@ -234,25 +235,14 @@ void MainWindow::openSystemFolder()
void MainWindow::viewLogs()
{
// Check for previous window.
if (m_logs) {
m_logs->activateWindow();
m_logs->raise();
return;
} else if (!m_kernel) {
QMessageBox::information(this, "Information", "The kernel is not running.");
return;
} else {
m_logs = new LogsViewer();
m_logs->setAttribute(Qt::WA_DeleteOnClose);
m_logs->show();
}
// Create a window.
m_logs = new LogsViewer();
vmm_logs(m_kernel, m_logs.get(), [](uint8_t, const char *msg, size_t len, void *cx) {
reinterpret_cast<LogsViewer *>(cx)->append(QString::fromUtf8(msg, len));
});
m_logs->setAttribute(Qt::WA_DeleteOnClose);
m_logs->show();
}
void MainWindow::reportIssue()
@ -332,7 +322,7 @@ void MainWindow::startKernel()
}
#endif
vmm = vmm_run(kernel.c_str(), &screen, &error);
vmm = vmm_run(kernel.c_str(), &screen, MainWindow::vmmHandler, this, &error);
if (!vmm) {
m_main->setCurrentIndex(0);
@ -373,6 +363,19 @@ void MainWindow::updateScreen()
m_screen->requestUpdate();
}
void MainWindow::log(VmmLog type, const QString &msg)
{
if (m_logs) {
m_logs->append(msg);
} else {
switch (type) {
case VmmLog_Info:
std::cout << msg.toStdString();
break;
}
}
}
bool MainWindow::loadGame(const QString &gameId)
{
auto gamesDirectory = readGamesDirectorySetting();
@ -453,3 +456,21 @@ bool MainWindow::requireEmulatorStopped()
return false;
}
bool MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
{
auto w = reinterpret_cast<MainWindow *>(cx);
switch (ev->tag) {
case VmmEvent_Log:
QMetaObject::invokeMethod(
w,
&MainWindow::log,
Qt::QueuedConnection,
ev->log.ty,
QString::fromUtf8(ev->log.data, ev->log.len));
break;
}
return true;
}

View File

@ -37,14 +37,17 @@ private slots:
void updateScreen();
private:
void log(VmmLog type, const QString &msg);
bool loadGame(const QString &gameId);
void restoreGeometry();
bool requireEmulatorStopped();
static bool vmmHandler(const VmmEvent *ev, void *cx);
QStackedWidget *m_main;
GameListModel *m_games;
LaunchSettings *m_launch;
Screen *m_screen;
QPointer<LogsViewer> m_logs;
Rust<Vmm> m_kernel;
Rust<Vmm> m_kernel; // Destroy first.
};

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anstyle = { version = "1.0.8", default-features = false }
macros = { path = "../macros" }
obconf = { path = "../obconf" }
obvirt = { path = "../obvirt" }

View File

@ -1,16 +1,17 @@
use crate::config::boot_env;
use core::fmt::Arguments;
use anstyle::{AnsiColor, Color, Style};
use core::fmt::{Arguments, Display, Formatter};
use obconf::BootEnv;
use obvirt::console::MsgType;
mod vm;
/// Write single line of information log.
/// Write information log.
///
/// When running inside a VM each call will cause a VM to exit multiple times so don't do this in a
/// performance critical path.
///
/// The line should not contains LF character.
/// The LF character will be automatically appended.
#[macro_export]
macro_rules! info {
($($args:tt)*) => {
@ -18,9 +19,37 @@ macro_rules! info {
};
}
#[doc(hidden)]
pub fn info(file: &str, line: u32, msg: Arguments) {
let log = Log {
style: Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightCyan))),
cat: 'I',
file,
line,
msg,
};
match boot_env() {
BootEnv::Vm(env) => self::vm::print(env, MsgType::Info, file, line, msg),
BootEnv::Vm(env) => self::vm::print(env, MsgType::Info, log),
}
}
/// [`Display`] implementation to format each log.
struct Log<'a> {
style: Style,
cat: char,
file: &'a str,
line: u32,
msg: Arguments<'a>,
}
impl<'a> Display for Log<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
writeln!(
f,
"{}++++++++++++++++++ {} {}:{}{0:#}",
self.style, self.cat, self.file, self.line
)?;
self.msg.fmt(f)?;
Ok(())
}
}

View File

@ -1,17 +1,15 @@
use core::fmt::{Arguments, Write};
use core::fmt::{Display, Write};
use core::ptr::{addr_of_mut, write_volatile};
use obconf::Vm;
use obvirt::console::{Commit, Memory, MsgType};
use obvirt::console::{Memory, MsgType};
pub fn print(env: &Vm, ty: MsgType, file: &str, line: u32, msg: Arguments) {
pub fn print(env: &Vm, ty: MsgType, msg: impl Display) {
let c = env.console as *mut Memory;
let mut w = Writer(c);
unsafe { write_volatile(addr_of_mut!((*c).file_len), file.len()) };
unsafe { write_volatile(addr_of_mut!((*c).file_addr), file.as_ptr() as usize) };
writeln!(w, "{msg}").unwrap();
Writer(c).write_fmt(msg).unwrap();
unsafe { write_volatile(addr_of_mut!((*c).commit), Commit::new(ty, line)) };
unsafe { write_volatile(addr_of_mut!((*c).commit), ty) };
}
struct Writer(*mut Memory);

View File

@ -2,41 +2,14 @@
///
/// The sequence of operations on a console memory is per-cpu. The kernel will start each log by:
///
/// 1. Write [`Self::file_len`] then [`Self::file_addr`].
/// 2. Write [`Self::msg_len`] then [`Self::msg_addr`].
/// 3. Repeat step 2 until the whole message has been written.
/// 4. Write [`Self::commit`].
/// 1. Write [`Self::msg_len`] then [`Self::msg_addr`].
/// 2. Repeat step 1 until the whole message has been written.
/// 3. Write [`Self::commit`].
#[repr(C)]
pub struct Memory {
pub file_len: usize,
pub file_addr: usize,
pub msg_len: usize,
pub msg_addr: usize,
pub commit: Commit,
}
/// Struct to commit a log.
#[repr(transparent)]
pub struct Commit(u32);
impl Commit {
/// # Panics
/// If `line` greater than 0xffffff.
pub fn new(ty: MsgType, line: u32) -> Self {
assert!(line <= 0xffffff);
Self((ty as u32) << 24 | line)
}
pub fn parse(raw: u32) -> Option<(MsgType, u32)> {
let line = raw & 0xffffff;
let ty = match (raw >> 24) as u8 {
v if v == MsgType::Info as u8 => MsgType::Info,
_ => return None,
};
Some((ty, line))
}
pub commit: MsgType,
}
/// Type of console message.
@ -45,3 +18,14 @@ impl Commit {
pub enum MsgType {
Info,
}
impl MsgType {
pub fn from_u8(v: u8) -> Option<Self> {
let v = match v {
v if v == MsgType::Info as u8 => MsgType::Info,
_ => return None,
};
Some(v)
}
}