mirror of
https://github.com/obhq/obliteration.git
synced 2024-11-23 03:09:52 +00:00
Initializes Slint gui (#1084)
Co-authored-by: tompro <tomas.prochazka@apertia.cz>
This commit is contained in:
parent
9aef904d24
commit
296cc3e502
6
.github/workflows/ci-mac.yml
vendored
6
.github/workflows/ci-mac.yml
vendored
@ -15,7 +15,7 @@ on:
|
||||
type: string
|
||||
required: true
|
||||
env:
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: '3'
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: "3"
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ inputs.name }}
|
||||
@ -25,6 +25,10 @@ jobs:
|
||||
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: Set up Python 3.12 (the latest version that is able to build skia-bindings)
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
- name: Generate cache keys
|
||||
|
26
.vscode/launch.json
vendored
26
.vscode/launch.json
vendored
@ -1,7 +1,7 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "GUI",
|
||||
"name": "GUI (Qt)",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
@ -22,6 +22,24 @@
|
||||
"program": "${workspaceFolder}/build/gui/obliteration.app/Contents/MacOS/obliteration"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GUI (Slint)",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path",
|
||||
"${workspaceFolder}/gui/Cargo.toml",
|
||||
"--features",
|
||||
"slint"
|
||||
],
|
||||
"filter": {
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Kernel",
|
||||
"type": "lldb",
|
||||
@ -31,10 +49,10 @@
|
||||
"target create ${workspaceFolder}/build/obkrnl",
|
||||
"target modules load --file ${workspaceFolder}/build/obkrnl -s 0xffffffff82200000"
|
||||
],
|
||||
"processCreateCommands": [
|
||||
"gdb-remote 1234"
|
||||
]
|
||||
"processCreateCommands": ["gdb-remote 1234"]
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +41,7 @@ else()
|
||||
endif()
|
||||
|
||||
add_cargo(MANIFEST Cargo.toml)
|
||||
add_crate(gui)
|
||||
add_crate(gui LIBRARY ARGS --features "qt")
|
||||
add_crate(obkrnl
|
||||
TOOLCHAIN ${kernel_toolchain}
|
||||
VENDOR "unknown"
|
||||
|
@ -178,6 +178,9 @@ function(add_crate crate)
|
||||
|
||||
if(${kind} STREQUAL "staticlib")
|
||||
add_library(${crate} STATIC IMPORTED)
|
||||
|
||||
list(APPEND build_args "--lib")
|
||||
|
||||
if(WIN32)
|
||||
set(debug_artifact "${debug_outputs}/${crate}.lib")
|
||||
set(release_artifact "${release_outputs}/${crate}.lib")
|
||||
|
@ -66,6 +66,10 @@ target_link_libraries(obliteration PRIVATE Qt6::Svg Qt6::Widgets)
|
||||
target_link_libraries(obliteration PRIVATE Threads::Threads)
|
||||
target_link_libraries(obliteration PRIVATE gui)
|
||||
|
||||
if(WIN32 OR (UNIX AND NOT APPLE))
|
||||
target_link_libraries(obliteration PRIVATE Vulkan::Vulkan)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(obliteration PRIVATE bcrypt imm32 ntdll setupapi userenv version winhvplatform winmm ws2_32)
|
||||
elseif(APPLE)
|
||||
|
@ -5,6 +5,20 @@ edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
required-features = ["qt"]
|
||||
|
||||
[[bin]]
|
||||
name = "obliteration"
|
||||
path = "src/main.rs"
|
||||
required-features = ["slint"]
|
||||
|
||||
[features]
|
||||
slint = [
|
||||
"dep:slint",
|
||||
"dep:clap",
|
||||
"dep:raw-window-handle",
|
||||
]
|
||||
qt = []
|
||||
|
||||
[dependencies]
|
||||
bitfield-struct = "0.8.0"
|
||||
@ -26,9 +40,18 @@ aarch64 = { path = "../arch/aarch64" }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
x86-64 = { path = "../arch/x86-64" }
|
||||
raw-window-handle = { version = "0.6", optional = true }
|
||||
clap = { version = "4.5.20", features = ["derive"], optional = true }
|
||||
|
||||
[dependencies.slint]
|
||||
git = "https://github.com/slint-ui/slint"
|
||||
rev = "875ca075fb5b2dfe4c3ab0a499d5759412fc1395"
|
||||
features = ["compat-1-2", "std", "accessibility", "raw-window-handle-06", "backend-winit", "renderer-skia"]
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
ash = "0.38.0"
|
||||
ash = { version = "0.38.0", features = ["linked", "std"], default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
@ -49,3 +72,4 @@ objc = "0.2.7"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26.0"
|
||||
slint-build = { git = "https://github.com/slint-ui/slint", rev = "875ca075fb5b2dfe4c3ab0a499d5759412fc1395" }
|
||||
|
20
gui/build.rs
20
gui/build.rs
@ -1,4 +1,5 @@
|
||||
use cbindgen::{Builder, Config, Language, Style};
|
||||
use slint_build::CompilerConfiguration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const LINUX_INCLUDE: &str = r#"
|
||||
@ -8,6 +9,25 @@ const LINUX_INCLUDE: &str = r#"
|
||||
"#;
|
||||
|
||||
fn main() {
|
||||
if std::env::var("CARGO_FEATURE_SLINT").is_ok_and(|var| var == "1") {
|
||||
build_bin();
|
||||
}
|
||||
|
||||
if std::env::var("CARGO_FEATURE_QT").is_ok_and(|var| var == "1") {
|
||||
build_lib();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bin() {
|
||||
// Compile main
|
||||
slint_build::compile_with_config(
|
||||
"slint/main.slint",
|
||||
CompilerConfiguration::new().with_style(String::from("fluent-dark")),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn build_lib() {
|
||||
let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let mut conf = Config::default();
|
||||
let mut buf = String::new();
|
||||
|
@ -166,7 +166,7 @@ int main(int argc, char *argv[])
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"Error",
|
||||
"No any Vulkan device supports Vulkan 1.3.");
|
||||
"No Vulkan device supports Vulkan 1.3.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
63
gui/slint/main.slint
Normal file
63
gui/slint/main.slint
Normal file
@ -0,0 +1,63 @@
|
||||
import { Game } from "structs.slint";
|
||||
import { Tabs } from "main/tabs.slint";
|
||||
import { Menu } from "main/menu.slint";
|
||||
import { Actions } from "main/actions.slint";
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
preferred-width: 1920px;
|
||||
preferred-height: 1080px;
|
||||
icon: @image-url("../resources/obliteration-icon.png");
|
||||
title: "Obliteration";
|
||||
|
||||
in property <[Game]> games: [];
|
||||
in property <[string]> profiles: [];
|
||||
in property <[string]> devices: [];
|
||||
|
||||
pure callback start_game(int);
|
||||
|
||||
pure callback clear_log();
|
||||
pure callback get_log_text() -> string;
|
||||
|
||||
pure callback open_new_issue_link();
|
||||
|
||||
pure callback install_pkg();
|
||||
pure callback open_system_folder();
|
||||
pure callback quit();
|
||||
|
||||
VerticalLayout {
|
||||
Menu {
|
||||
background: root.background;
|
||||
popup_width: root.width / 2;
|
||||
popup_height: root.height / 2;
|
||||
popup_x: root.width / 4;
|
||||
popup_y: root.height / 4;
|
||||
|
||||
quit => { quit(); }
|
||||
open_new_issue_link => { open_new_issue_link(); }
|
||||
install_pkg => { install_pkg(); }
|
||||
open_system_folder => { open_system_folder(); }
|
||||
}
|
||||
Tabs {
|
||||
devices: devices;
|
||||
games: games;
|
||||
select_game(index) => { }
|
||||
}
|
||||
Actions {
|
||||
profiles: profiles;
|
||||
start_game => {
|
||||
start_game(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component Screen inherits Window {}
|
||||
|
||||
export component ErrorDialog inherits Window {
|
||||
in property <string> message;
|
||||
|
||||
Text {
|
||||
padding: 10px;
|
||||
text: message;
|
||||
}
|
||||
}
|
29
gui/slint/main/actions.slint
Normal file
29
gui/slint/main/actions.slint
Normal file
@ -0,0 +1,29 @@
|
||||
import { ComboBox, Button } from "std-widgets.slint";
|
||||
|
||||
export component Actions {
|
||||
in property <[string]> profiles: [];
|
||||
|
||||
pure callback start_game();
|
||||
|
||||
HorizontalLayout {
|
||||
|
||||
alignment: stretch;
|
||||
spacing: 5px;
|
||||
|
||||
ComboBox {
|
||||
model: profiles;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
Button {
|
||||
text: "Start";
|
||||
horizontal-stretch: 0;
|
||||
icon: @image-url("../../resources/play.svg");
|
||||
clicked => { start_game(); }
|
||||
}
|
||||
Button {
|
||||
text: "Save";
|
||||
horizontal-stretch: 0;
|
||||
icon: @image-url("../../resources/content-save.svg");
|
||||
}
|
||||
}
|
||||
}
|
178
gui/slint/main/menu.slint
Normal file
178
gui/slint/main/menu.slint
Normal file
@ -0,0 +1,178 @@
|
||||
import { Button, StandardButton, AboutSlint } from "std-widgets.slint";
|
||||
export component Menu {
|
||||
in property <brush> background;
|
||||
in property <length> popup_width;
|
||||
in property <length> popup_height;
|
||||
in property <length> popup_x;
|
||||
in property <length> popup_y;
|
||||
|
||||
pure callback quit();
|
||||
pure callback open_new_issue_link();
|
||||
pure callback open_system_folder();
|
||||
pure callback install_pkg();
|
||||
|
||||
install_pkg_dialog := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
x: popup_x;
|
||||
y: popup_y;
|
||||
|
||||
Dialog {
|
||||
Text {
|
||||
text: "This is a dialog box";
|
||||
}
|
||||
StandardButton { kind: ok; }
|
||||
StandardButton { kind: cancel; }
|
||||
}
|
||||
}
|
||||
|
||||
about_dialog := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
x: popup_x;
|
||||
y: popup_y;
|
||||
|
||||
Rectangle {
|
||||
Dialog {
|
||||
Text {
|
||||
text: "Obliteration is a free and open-source software for playing your PlayStation 4 titles on PC.";
|
||||
}
|
||||
StandardButton { kind: ok; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
about_slint := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
x: popup_x;
|
||||
y: popup_y;
|
||||
|
||||
Rectangle {
|
||||
Dialog {
|
||||
AboutSlint {}
|
||||
StandardButton { kind: ok; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logs := PopupWindow {
|
||||
width: popup_width;
|
||||
height: popup_height;
|
||||
x: popup_x;
|
||||
y: popup_y;
|
||||
|
||||
Rectangle {
|
||||
Text {
|
||||
text: "Obliteration is a free and open-source software for playing your PlayStation 4 titles on PC.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: start;
|
||||
spacing: 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
VerticalLayout {
|
||||
TouchArea {
|
||||
clicked => { files_popup.show(); }
|
||||
|
||||
Rectangle {
|
||||
Text {
|
||||
text: "File";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files_popup := PopupWindow {
|
||||
y: parent.y + parent.height;
|
||||
|
||||
Rectangle {
|
||||
background: background;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
Button {
|
||||
text: "Install PKG";
|
||||
clicked => { install_pkg(); }
|
||||
}
|
||||
Button {
|
||||
text: "Open System Folder";
|
||||
clicked => { open_system_folder(); }
|
||||
}
|
||||
Button {
|
||||
text: "Quit";
|
||||
clicked => { quit() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
TouchArea {
|
||||
clicked => { view_popup.show(); }
|
||||
|
||||
Rectangle {
|
||||
Text {
|
||||
text: "View";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_popup := PopupWindow {
|
||||
y: parent.y + parent.height;
|
||||
|
||||
Rectangle {
|
||||
background: background;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
Button {
|
||||
text: "Logs";
|
||||
clicked => { logs.show(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
TouchArea {
|
||||
clicked => { help_popup.show(); }
|
||||
|
||||
Rectangle {
|
||||
Text {
|
||||
text: "Help";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
help_popup := PopupWindow {
|
||||
y: parent.y + parent.height;
|
||||
|
||||
Rectangle {
|
||||
background: background;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
Button {
|
||||
text: "Report an Issue";
|
||||
clicked => { open_new_issue_link(); }
|
||||
}
|
||||
Button {
|
||||
text: "About Slint";
|
||||
clicked => { about_slint.show(); }
|
||||
}
|
||||
Button {
|
||||
text: "About Obliteration";
|
||||
clicked => { about_dialog.show(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
gui/slint/main/tabs.slint
Normal file
43
gui/slint/main/tabs.slint
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
import { TabWidget } from "std-widgets.slint";
|
||||
import { DisplayTab } from "tabs/display.slint";
|
||||
import { CpuTab } from "tabs/cpu.slint";
|
||||
import { GamesTab } from "tabs/games.slint";
|
||||
import { Game } from "../structs.slint";
|
||||
|
||||
export component Tabs {
|
||||
in property <[string]> devices: [];
|
||||
in property <[Game]> games: [];
|
||||
|
||||
pure callback select_game(int);
|
||||
|
||||
TabWidget {
|
||||
Tab {
|
||||
title: "Display";
|
||||
//icon: @image-url("resources/darkmode/card-text-outline.svg");
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
DisplayTab { devices: devices; }
|
||||
}
|
||||
}
|
||||
Tab {
|
||||
title: "CPU";
|
||||
//icon: @image-url("resources/darkmode/card-text-outline.svg");
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
CpuTab {}
|
||||
}
|
||||
}
|
||||
Tab {
|
||||
title: "Games";
|
||||
//icon: @image-url("resources/darkmode/view-comfy.png");
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
GamesTab { games: games; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
gui/slint/main/tabs/cpu.slint
Normal file
49
gui/slint/main/tabs/cpu.slint
Normal file
@ -0,0 +1,49 @@
|
||||
import { Button, Slider, LineEdit } from "std-widgets.slint";
|
||||
|
||||
export component CpuTab {
|
||||
HorizontalLayout {
|
||||
VerticalLayout {
|
||||
padding: 10px;
|
||||
vertical-stretch: 0;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "Count";
|
||||
}
|
||||
Slider {
|
||||
value: 8;
|
||||
minimum: 1;
|
||||
maximum: 16;
|
||||
}
|
||||
Text {
|
||||
text: "Changing this value to other than 8 may crash the game.";
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
padding: 10px;
|
||||
vertical-stretch: 0;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "Debug";
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 5px;
|
||||
Text {
|
||||
text: "Listen address";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
LineEdit {
|
||||
text: "127.0.0.1";
|
||||
max-height: 30px;
|
||||
}
|
||||
Button {
|
||||
text: "Start";
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: "Specify a TCP address to listen for a debugger. The kernel will wait for a debugger to connect before start.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
gui/slint/main/tabs/display.slint
Normal file
37
gui/slint/main/tabs/display.slint
Normal file
@ -0,0 +1,37 @@
|
||||
import { ComboBox } from "std-widgets.slint";
|
||||
|
||||
export component DisplayTab {
|
||||
in property <[string]> devices: [];
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 10px;
|
||||
spacing: 10px;
|
||||
VerticalLayout {
|
||||
vertical-stretch: 0;
|
||||
spacing: 10px;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "Device";
|
||||
}
|
||||
ComboBox {
|
||||
model: devices;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
VerticalLayout {
|
||||
vertical-stretch: 0;
|
||||
spacing: 10px;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "Resolution";
|
||||
}
|
||||
ComboBox {
|
||||
model: ["1280 × 720", "1920 × 1080", "3840 × 2160"];
|
||||
current-value: "1920 × 1080";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
gui/slint/main/tabs/games.slint
Normal file
57
gui/slint/main/tabs/games.slint
Normal file
@ -0,0 +1,57 @@
|
||||
import { Game } from "../../structs.slint";
|
||||
|
||||
export component GamesTab {
|
||||
in-out property <[Game]> games: [];
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: stretch;
|
||||
height: 30px;
|
||||
|
||||
Text {
|
||||
text: "Name";
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: "ID";
|
||||
width: 100px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
for game[i] in games: HorizontalLayout {
|
||||
alignment: stretch;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: stretch;
|
||||
height: 30px;
|
||||
|
||||
Rectangle {
|
||||
width: 15px;
|
||||
|
||||
Text {
|
||||
text: i;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
Image {
|
||||
source: game.icon;
|
||||
width: 30px;
|
||||
}
|
||||
Text {
|
||||
padding-left: 10px;
|
||||
text: game.name;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: game.id;
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
gui/slint/structs.slint
Normal file
6
gui/slint/structs.slint
Normal file
@ -0,0 +1,6 @@
|
||||
export struct Game {
|
||||
id: string,
|
||||
name: string,
|
||||
dir: string,
|
||||
icon: image,
|
||||
}
|
25
gui/src/args.rs
Normal file
25
gui/src/args.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct CliArgs {
|
||||
#[arg(long, help = "Immediate launch the VMM in debug mode.")]
|
||||
debug: Option<SocketAddrV4>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use the kernel image at the specified path instead of the default one."
|
||||
)]
|
||||
kernel: Option<Box<Path>>,
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
pub fn debug_addr(&self) -> Option<SocketAddrV4> {
|
||||
self.debug
|
||||
}
|
||||
|
||||
pub fn kernel_path(&self) -> Option<&Path> {
|
||||
self.kernel.as_deref()
|
||||
}
|
||||
}
|
@ -2,11 +2,6 @@
|
||||
use std::io::{Error, ErrorKind, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_client_free(d: *mut DebugClient) {
|
||||
drop(Box::from_raw(d));
|
||||
}
|
||||
|
||||
/// Encapsulate a debugger connection.
|
||||
pub struct DebugClient {
|
||||
sock: TcpStream,
|
||||
@ -75,13 +70,10 @@ impl gdbstub::conn::Connection for DebugClient {
|
||||
while !buf.is_empty() {
|
||||
let written = match Write::write(&mut self.sock, buf) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
Err(e) if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
if written == 0 {
|
||||
|
66
gui/src/debug/ffi.rs
Normal file
66
gui/src/debug/ffi.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use super::{DebugClient, DebugServer};
|
||||
use crate::error::RustError;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_start(
|
||||
addr: *const c_char,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut DebugServer {
|
||||
// Get address.
|
||||
let addr = match CStr::from_ptr(addr).to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
*err = RustError::new("the specified address is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Start server.
|
||||
match DebugServer::new(addr) {
|
||||
Ok(server) => Box::into_raw(Box::new(server)),
|
||||
Err(e) => {
|
||||
*err = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_free(s: *mut DebugServer) {
|
||||
drop(Box::from_raw(s));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_addr(s: *mut DebugServer) -> *const c_char {
|
||||
(*s).addr.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_socket(s: *mut DebugServer) -> isize {
|
||||
#[cfg(unix)]
|
||||
return std::os::fd::AsRawFd::as_raw_fd(&(*s).sock) as _;
|
||||
|
||||
#[cfg(windows)]
|
||||
return std::os::windows::io::AsRawSocket::as_raw_socket(&(*s).sock) as _;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_accept(
|
||||
s: *mut DebugServer,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut DebugClient {
|
||||
match (*s).accept() {
|
||||
Ok(client) => Box::into_raw(Box::new(client)),
|
||||
Err(e) => {
|
||||
*err = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_client_free(d: *mut DebugClient) {
|
||||
drop(Box::from_raw(d));
|
||||
}
|
@ -1,84 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
pub use self::client::*;
|
||||
|
||||
use crate::error::RustError;
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
use std::net::TcpListener;
|
||||
use std::ptr::null_mut;
|
||||
use std::ffi::CString;
|
||||
use std::net::{TcpListener, ToSocketAddrs};
|
||||
use thiserror::Error;
|
||||
|
||||
mod client;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_start(
|
||||
addr: *const c_char,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut DebugServer {
|
||||
// Get address.
|
||||
let addr = match CStr::from_ptr(addr).to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
*err = RustError::new("the specified address is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Start server.
|
||||
let sock = match TcpListener::bind(addr) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't bind to the specified address", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Get effective address to let the user know.
|
||||
let addr = match sock.local_addr() {
|
||||
Ok(v) => CString::new(v.to_string()).unwrap(),
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't get server address", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Return server object.
|
||||
Box::into_raw(Box::new(DebugServer { addr, sock }))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_free(s: *mut DebugServer) {
|
||||
drop(Box::from_raw(s));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_addr(s: *mut DebugServer) -> *const c_char {
|
||||
(*s).addr.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_socket(s: *mut DebugServer) -> isize {
|
||||
#[cfg(unix)]
|
||||
return std::os::fd::AsRawFd::as_raw_fd(&(*s).sock) as _;
|
||||
|
||||
#[cfg(windows)]
|
||||
return std::os::windows::io::AsRawSocket::as_raw_socket(&(*s).sock) as _;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn debug_server_accept(
|
||||
s: *mut DebugServer,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut DebugClient {
|
||||
match (*s).sock.accept() {
|
||||
Ok((sock, _)) => Box::into_raw(Box::new(DebugClient::new(sock))),
|
||||
Err(e) => {
|
||||
*err = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
|
||||
/// TCP listener to accept a debugger connection.
|
||||
pub struct DebugServer {
|
||||
addr: CString,
|
||||
sock: TcpListener,
|
||||
}
|
||||
|
||||
impl DebugServer {
|
||||
pub fn new(addr: impl ToSocketAddrs) -> Result<Self, StartDebugServerError> {
|
||||
let sock = TcpListener::bind(addr).map_err(StartDebugServerError::BindFailed)?;
|
||||
let addr = sock
|
||||
.local_addr()
|
||||
.map_err(StartDebugServerError::GetAddrFailed)?;
|
||||
|
||||
Ok(Self {
|
||||
addr: CString::new(addr.to_string()).unwrap(),
|
||||
sock,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn accept(&self) -> std::io::Result<DebugClient> {
|
||||
let (sock, _) = self.sock.accept()?;
|
||||
|
||||
Ok(DebugClient::new(sock))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StartDebugServerError {
|
||||
#[error("couldn't bind to the specified address")]
|
||||
BindFailed(#[source] std::io::Error),
|
||||
|
||||
#[error("couldn't get server address")]
|
||||
GetAddrFailed(#[source] std::io::Error),
|
||||
}
|
||||
|
12
gui/src/error/ffi.rs
Normal file
12
gui/src/error/ffi.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use super::RustError;
|
||||
use std::ffi::c_char;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn error_free(e: *mut RustError) {
|
||||
drop(Box::from_raw(e));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn error_message(e: *const RustError) -> *const c_char {
|
||||
(*e).0.as_ptr()
|
||||
}
|
@ -1,17 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::ffi::CString;
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn error_free(e: *mut RustError) {
|
||||
drop(Box::from_raw(e));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn error_message(e: *const RustError) -> *const c_char {
|
||||
(*e).0.as_ptr()
|
||||
}
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
|
||||
/// Error object managed by Rust side.
|
||||
pub struct RustError(CString);
|
||||
|
@ -11,6 +11,7 @@ mod string;
|
||||
mod system;
|
||||
mod vmm;
|
||||
|
||||
#[cfg(feature = "qt")]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C-unwind" fn set_panic_hook(
|
||||
cx: *mut c_void,
|
||||
|
177
gui/src/main.rs
Normal file
177
gui/src/main.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use args::CliArgs;
|
||||
use clap::Parser;
|
||||
use debug::DebugServer;
|
||||
use slint::{ComponentHandle, ModelExt, ModelRc, SharedString, VecModel};
|
||||
use std::process::{ExitCode, Termination};
|
||||
use thiserror::Error;
|
||||
|
||||
mod args;
|
||||
mod debug;
|
||||
mod error;
|
||||
mod param;
|
||||
mod pkg;
|
||||
mod profile;
|
||||
#[cfg(unix)]
|
||||
mod rlim;
|
||||
mod screen;
|
||||
mod string;
|
||||
mod system;
|
||||
mod ui;
|
||||
mod vmm;
|
||||
|
||||
fn main() -> AppExit {
|
||||
let res = run();
|
||||
|
||||
AppExit::from(res)
|
||||
}
|
||||
|
||||
fn run() -> Result<(), ApplicationError> {
|
||||
#[cfg(unix)]
|
||||
if let Err(e) = rlim::set_rlimit_nofile() {
|
||||
ui::ErrorDialog::new()
|
||||
.and_then(|error_dialog| {
|
||||
error_dialog.set_message(SharedString::from(format!(
|
||||
"Error setting rlimit: {}",
|
||||
full_error_reason(e)
|
||||
)));
|
||||
|
||||
error_dialog.run()
|
||||
})
|
||||
.inspect_err(|e| eprintln!("Error displaying error dialog: {e}"))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let args = CliArgs::try_parse().map_err(ApplicationError::ParseArgs)?;
|
||||
|
||||
if let Some(debug_addr) = args.debug_addr() {
|
||||
let debug_server = DebugServer::new(debug_addr)
|
||||
.map_err(|e| ApplicationError::StartDebugServer(e, debug_addr))?;
|
||||
|
||||
let debug_client = debug_server
|
||||
.accept()
|
||||
.map_err(ApplicationError::CreateDebugClient)?;
|
||||
}
|
||||
|
||||
let app = App::new()?;
|
||||
|
||||
app.run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct App {
|
||||
main_window: ui::MainWindow,
|
||||
|
||||
games: ModelRc<ui::Game>,
|
||||
profiles: ModelRc<SharedString>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> Result<Self, ApplicationError> {
|
||||
let main_window = ui::MainWindow::new().map_err(ApplicationError::CreateMainWindow)?;
|
||||
|
||||
let games = ModelRc::new(VecModel::from(Vec::new()));
|
||||
|
||||
main_window.set_games(games.clone());
|
||||
|
||||
let profiles = ModelRc::new(
|
||||
VecModel::from(vec![profile::Profile::default()])
|
||||
.map(|p| SharedString::from(String::from(p.name().to_string_lossy()))),
|
||||
);
|
||||
|
||||
main_window.set_profiles(profiles.clone());
|
||||
|
||||
main_window.on_start_game(|_index| {
|
||||
let screen = ui::Screen::new().unwrap();
|
||||
|
||||
screen.show().unwrap();
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
main_window,
|
||||
games,
|
||||
profiles,
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self) -> Result<(), ApplicationError> {
|
||||
self.main_window
|
||||
.run()
|
||||
.map_err(ApplicationError::RunMainWindow)
|
||||
}
|
||||
}
|
||||
|
||||
fn full_error_reason<T>(e: T) -> String
|
||||
where
|
||||
T: std::error::Error,
|
||||
{
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut msg = format!("{e}");
|
||||
let mut src = e.source();
|
||||
|
||||
while let Some(e) = src {
|
||||
write!(&mut msg, " -> {e}").unwrap();
|
||||
src = e.source();
|
||||
}
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
pub enum AppExit {
|
||||
Ok,
|
||||
Err(ApplicationError),
|
||||
}
|
||||
|
||||
impl Termination for AppExit {
|
||||
fn report(self) -> ExitCode {
|
||||
match self {
|
||||
AppExit::Ok => ExitCode::SUCCESS,
|
||||
AppExit::Err(e) => {
|
||||
ui::ErrorDialog::new()
|
||||
.and_then(|error_dialog| {
|
||||
error_dialog.set_message(SharedString::from(format!(
|
||||
"Error running application: {}",
|
||||
full_error_reason(e)
|
||||
)));
|
||||
|
||||
error_dialog.run()
|
||||
})
|
||||
.inspect_err(|e| eprintln!("Error displaying error dialog: {e}"))
|
||||
.unwrap();
|
||||
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Result<(), ApplicationError>> for AppExit {
|
||||
fn from(v: Result<(), ApplicationError>) -> Self {
|
||||
match v {
|
||||
Ok(_) => AppExit::Ok,
|
||||
Err(e) => AppExit::Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ApplicationError {
|
||||
#[error(transparent)]
|
||||
ParseArgs(clap::Error),
|
||||
|
||||
#[error("failed to start debug server on {1}")]
|
||||
StartDebugServer(
|
||||
#[source] debug::StartDebugServerError,
|
||||
std::net::SocketAddrV4,
|
||||
),
|
||||
|
||||
#[error("failed to accept debug client")]
|
||||
CreateDebugClient(#[source] std::io::Error),
|
||||
|
||||
#[error("failed to create main window")]
|
||||
CreateMainWindow(#[source] slint::PlatformError),
|
||||
|
||||
#[error("failed to run main window")]
|
||||
RunMainWindow(#[source] slint::PlatformError),
|
||||
}
|
58
gui/src/param/ffi.rs
Normal file
58
gui/src/param/ffi.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use crate::error::RustError;
|
||||
use crate::string::strdup;
|
||||
use param::Param;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_open(file: *const c_char, error: *mut *mut RustError) -> *mut Param {
|
||||
let path = CStr::from_ptr(file).to_str().unwrap();
|
||||
|
||||
match Param::open(path) {
|
||||
Ok(param) => Box::into_raw(Box::new(param)),
|
||||
Err(e) => {
|
||||
*error = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_close(p: *mut Param) {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_app_ver_get(p: *const Param) -> *mut c_char {
|
||||
(*p).app_ver().map(strdup).unwrap_or(null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_category_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).category())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_content_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).content_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_short_content_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).shortcontent_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_get(p: *const Param) -> *mut c_char {
|
||||
(*p).title().map(strdup).unwrap_or(null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).title_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_version_get(p: *const Param) -> *mut c_char {
|
||||
(*p).version().map(strdup).unwrap_or(null_mut())
|
||||
}
|
@ -1,69 +1,2 @@
|
||||
use crate::error::RustError;
|
||||
use crate::string::strdup;
|
||||
use param::Param;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::fs::File;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_open(file: *const c_char, error: *mut *mut RustError) -> *mut Param {
|
||||
// Open file.
|
||||
let file = match File::open(CStr::from_ptr(file).to_str().unwrap()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*error = RustError::with_source("couldn't open the specified file", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Parse.
|
||||
let param = match Param::read(file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*error = RustError::with_source("couldn't read the specified file", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(param.into())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_close(p: *mut Param) {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_app_ver_get(p: *const Param) -> *mut c_char {
|
||||
(*p).app_ver().map(strdup).unwrap_or(null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_category_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).category())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_content_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).content_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_short_content_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).shortcontent_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_get(p: *const Param) -> *mut c_char {
|
||||
(*p).title().map(strdup).unwrap_or(null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_id_get(p: *const Param) -> *mut c_char {
|
||||
strdup((*p).title_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_version_get(p: *const Param) -> *mut c_char {
|
||||
(*p).version().map(strdup).unwrap_or(null_mut())
|
||||
}
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
|
58
gui/src/pkg/ffi.rs
Normal file
58
gui/src/pkg/ffi.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use super::ExtractProgress;
|
||||
use crate::error::RustError;
|
||||
use param::Param;
|
||||
use pkg::Pkg;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::path::Path;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut RustError) -> *mut Pkg {
|
||||
let path = CStr::from_ptr(file).to_str().unwrap();
|
||||
|
||||
match Pkg::open(path) {
|
||||
Ok(pkg) => Box::into_raw(Box::new(pkg)),
|
||||
Err(e) => {
|
||||
*error = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) {
|
||||
drop(Box::from_raw(pkg));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_get_param(pkg: *const Pkg, error: *mut *mut RustError) -> *mut Param {
|
||||
match (*pkg).get_param() {
|
||||
Ok(param) => Box::into_raw(Box::new(param)),
|
||||
Err(e) => {
|
||||
*error = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_extract(
|
||||
pkg: *const Pkg,
|
||||
dir: *const c_char,
|
||||
status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> *mut RustError {
|
||||
let root: &Path = CStr::from_ptr(dir).to_str().unwrap().as_ref();
|
||||
let progress = ExtractProgress {
|
||||
status,
|
||||
ud,
|
||||
root,
|
||||
total: 0,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
match (*pkg).extract(root, progress) {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => RustError::wrap(e).into_c(),
|
||||
}
|
||||
}
|
@ -1,64 +1,11 @@
|
||||
use crate::error::RustError;
|
||||
use humansize::{SizeFormatter, DECIMAL};
|
||||
use param::Param;
|
||||
use pkg::{Pkg, PkgProgress};
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use pkg::PkgProgress;
|
||||
use std::ffi::{c_char, c_void, CString};
|
||||
use std::path::Path;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::ptr::null;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut RustError) -> *mut Pkg {
|
||||
let path = CStr::from_ptr(file);
|
||||
let pkg = match Pkg::open(path.to_str().unwrap()) {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = RustError::wrap(e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(pkg)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) {
|
||||
drop(Box::from_raw(pkg));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_get_param(pkg: *const Pkg, error: *mut *mut RustError) -> *mut Param {
|
||||
let param = match (*pkg).get_param() {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = RustError::wrap(e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(param)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_extract(
|
||||
pkg: *const Pkg,
|
||||
dir: *const c_char,
|
||||
status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> *mut RustError {
|
||||
let root: &Path = CStr::from_ptr(dir).to_str().unwrap().as_ref();
|
||||
let progress = ExtractProgress {
|
||||
status,
|
||||
ud,
|
||||
root,
|
||||
total: 0,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
match (*pkg).extract(root, progress) {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => RustError::wrap(e).into_c(),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
|
||||
struct ExtractProgress<'a> {
|
||||
status: extern "C" fn(*const c_char, usize, u64, u64, *mut c_void),
|
||||
@ -87,7 +34,7 @@ impl<'a> PkgProgress for ExtractProgress<'a> {
|
||||
let total = total.try_into().unwrap();
|
||||
|
||||
(self.status)(
|
||||
b"Entries extraction completed\0".as_ptr().cast(), // https://github.com/mozilla/cbindgen/issues/927
|
||||
b"Entries extraction completed\0".as_ptr().cast(),
|
||||
0,
|
||||
total,
|
||||
total,
|
||||
@ -128,7 +75,7 @@ impl<'a> PkgProgress for ExtractProgress<'a> {
|
||||
|
||||
fn pfs_completed(&mut self) {
|
||||
(self.status)(
|
||||
b"PFS extraction completed\0".as_ptr().cast(), // https://github.com/mozilla/cbindgen/issues/927
|
||||
b"PFS extraction completed\0".as_ptr().cast(),
|
||||
0,
|
||||
self.total,
|
||||
self.total,
|
||||
|
77
gui/src/profile/ffi.rs
Normal file
77
gui/src/profile/ffi.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use super::{DisplayResolution, Profile};
|
||||
use crate::error::RustError;
|
||||
use crate::string::strdup;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::path::Path;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_new(name: *const c_char) -> *mut Profile {
|
||||
Box::into_raw(Box::new(Profile {
|
||||
name: CStr::from_ptr(name).to_owned(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_load(
|
||||
path: *const c_char,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Profile {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => {
|
||||
*err = RustError::new("the specified path is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
match Profile::load(root) {
|
||||
Ok(v) => Box::into_raw(Box::new(v)),
|
||||
Err(e) => {
|
||||
*err = RustError::wrap(e).into_c();
|
||||
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_free(p: *mut Profile) {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_id(p: *const Profile) -> *mut c_char {
|
||||
strdup((*p).id.to_string())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_name(p: *const Profile) -> *const c_char {
|
||||
(*p).name.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_display_resolution(p: *const Profile) -> DisplayResolution {
|
||||
(*p).display_resolution
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_set_display_resolution(p: *mut Profile, v: DisplayResolution) {
|
||||
(*p).display_resolution = v;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_save(p: *const Profile, path: *const c_char) -> *mut RustError {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => return RustError::new("the specified path is not UTF-8").into_c(),
|
||||
};
|
||||
|
||||
match (*p).save(root) {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => RustError::wrap(e).into_c(),
|
||||
}
|
||||
}
|
@ -1,120 +1,18 @@
|
||||
use crate::error::RustError;
|
||||
use crate::string::strdup;
|
||||
use obconf::Config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fs::File;
|
||||
use std::num::NonZero;
|
||||
use std::path::Path;
|
||||
use std::ptr::null_mut;
|
||||
use std::time::SystemTime;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_new(name: *const c_char) -> *mut Profile {
|
||||
Box::into_raw(Box::new(Profile {
|
||||
name: CStr::from_ptr(name).to_owned(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_load(
|
||||
path: *const c_char,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Profile {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => {
|
||||
*err = RustError::new("the specified path is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Open profile.bin.
|
||||
let path = root.join("profile.bin");
|
||||
let file = match File::open(&path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't open {}", path.display()), e)
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Load profile.bin.
|
||||
let p = match ciborium::from_reader(file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't load {}", path.display()), e)
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(Box::new(p))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_free(p: *mut Profile) {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_id(p: *const Profile) -> *mut c_char {
|
||||
strdup((*p).id.to_string())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_name(p: *const Profile) -> *const c_char {
|
||||
(*p).name.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_display_resolution(p: *const Profile) -> DisplayResolution {
|
||||
(*p).display_resolution
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_set_display_resolution(p: *mut Profile, v: DisplayResolution) {
|
||||
(*p).display_resolution = v;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_save(p: *const Profile, path: *const c_char) -> *mut RustError {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => return RustError::new("the specified path is not UTF-8").into_c(),
|
||||
};
|
||||
|
||||
// Create a directory.
|
||||
if let Err(e) = std::fs::create_dir_all(root) {
|
||||
return RustError::with_source("couldn't create the specified path", e).into_c();
|
||||
}
|
||||
|
||||
// Create profile.bin.
|
||||
let path = root.join("profile.bin");
|
||||
let file = match File::create(&path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return RustError::with_source(format_args!("couldn't create {}", path.display()), e)
|
||||
.into_c()
|
||||
}
|
||||
};
|
||||
|
||||
// Write profile.bin.
|
||||
if let Err(e) = ciborium::into_writer(&*p, file) {
|
||||
return RustError::with_source(format_args!("couldn't write {}", path.display()), e)
|
||||
.into_c();
|
||||
}
|
||||
|
||||
null_mut()
|
||||
}
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
|
||||
/// Contains settings to launch the kernel.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct Profile {
|
||||
id: Uuid,
|
||||
@ -125,6 +23,33 @@ pub struct Profile {
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<Self, LoadError> {
|
||||
let path = path.as_ref().join("profile.bin");
|
||||
|
||||
let file = File::open(&path).map_err(LoadError::Open)?;
|
||||
|
||||
let profile = ciborium::from_reader(file).map_err(LoadError::Load)?;
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), SaveError> {
|
||||
let path = path.as_ref();
|
||||
|
||||
std::fs::create_dir_all(&path).map_err(SaveError::CreateDir)?;
|
||||
|
||||
let path = path.join("profile.bin");
|
||||
|
||||
let file = File::create(&path).map_err(SaveError::CreateFile)?;
|
||||
|
||||
ciborium::into_writer(self, file).map_err(SaveError::WriteFile)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &CStr {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn kernel_config(&self) -> &Config {
|
||||
&self.kernel_config
|
||||
}
|
||||
@ -155,3 +80,24 @@ pub enum DisplayResolution {
|
||||
/// 3840 × 2160.
|
||||
UltraHd,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LoadError {
|
||||
#[error("couldn't open the profile file")]
|
||||
Open(#[source] std::io::Error),
|
||||
|
||||
#[error("couldn't load the profile file")]
|
||||
Load(#[source] ciborium::de::Error<std::io::Error>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SaveError {
|
||||
#[error("couldn't create the directory")]
|
||||
CreateDir(#[source] std::io::Error),
|
||||
|
||||
#[error("couldn't create the profile file")]
|
||||
CreateFile(#[source] std::io::Error),
|
||||
|
||||
#[error("couldn't write the profile file")]
|
||||
WriteFile(#[source] ciborium::ser::Error<std::io::Error>),
|
||||
}
|
||||
|
34
gui/src/rlim.rs
Normal file
34
gui/src/rlim.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::mem::MaybeUninit;
|
||||
use thiserror::Error;
|
||||
|
||||
pub(crate) fn set_rlimit_nofile() -> Result<(), RlimitError> {
|
||||
let mut rlim = MaybeUninit::uninit();
|
||||
|
||||
let ret = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, rlim.as_mut_ptr()) };
|
||||
|
||||
match ret {
|
||||
0 => {
|
||||
let mut rlim = unsafe { rlim.assume_init() };
|
||||
|
||||
if rlim.rlim_cur < rlim.rlim_max {
|
||||
rlim.rlim_cur = rlim.rlim_max;
|
||||
|
||||
if unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) } != 0 {
|
||||
return Err(RlimitError::SetRlimitFailed(std::io::Error::last_os_error()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(RlimitError::GetRlimitFailed(std::io::Error::last_os_error())),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum RlimitError {
|
||||
#[error("failed to get RLIMIT_NOFILE")]
|
||||
GetRlimitFailed(#[source] std::io::Error),
|
||||
|
||||
#[error("failed to set RLIMIT_NOFILE")]
|
||||
SetRlimitFailed(#[source] std::io::Error),
|
||||
}
|
@ -22,7 +22,7 @@ pub struct Metal {
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
pub fn new(screen: &VmmScreen) -> Result<Self, MetalError> {
|
||||
pub fn from_screen(screen: &VmmScreen) -> Result<Self, MetalError> {
|
||||
// Get Metal device.
|
||||
let device = match Device::system_default() {
|
||||
Some(v) => v,
|
||||
|
@ -12,6 +12,12 @@ pub type Default = self::engine::Vulkan;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type Default = self::engine::Metal;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub type ScreenError = self::engine::VulkanError;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type ScreenError = self::engine::MetalError;
|
||||
|
||||
/// Encapsulates a platform-specific surface for drawing a VM screen.
|
||||
pub trait Screen: 'static {
|
||||
type Buffer: ScreenBuffer;
|
||||
|
@ -1,186 +0,0 @@
|
||||
use ash::vk::{
|
||||
AllocationCallbacks, Device, DeviceCreateInfo, ExtensionProperties, ExternalBufferProperties,
|
||||
ExternalFenceProperties, ExternalSemaphoreProperties, Format, FormatProperties,
|
||||
FormatProperties2, ImageCreateFlags, ImageFormatProperties, ImageFormatProperties2,
|
||||
ImageTiling, ImageType, ImageUsageFlags, Instance, LayerProperties, PFN_vkVoidFunction,
|
||||
PhysicalDevice, PhysicalDeviceExternalBufferInfo, PhysicalDeviceExternalFenceInfo,
|
||||
PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceFeatures, PhysicalDeviceFeatures2,
|
||||
PhysicalDeviceGroupProperties, PhysicalDeviceImageFormatInfo2, PhysicalDeviceMemoryProperties,
|
||||
PhysicalDeviceMemoryProperties2, PhysicalDeviceProperties, PhysicalDeviceProperties2,
|
||||
PhysicalDeviceSparseImageFormatInfo2, PhysicalDeviceToolProperties, QueueFamilyProperties,
|
||||
QueueFamilyProperties2, Result, SampleCountFlags, SparseImageFormatProperties,
|
||||
SparseImageFormatProperties2,
|
||||
};
|
||||
use std::ffi::c_char;
|
||||
|
||||
extern "system" {
|
||||
#[link_name = "vmm_vk_create_device"]
|
||||
pub fn create_device(
|
||||
physical_device: PhysicalDevice,
|
||||
p_create_info: *const DeviceCreateInfo<'_>,
|
||||
p_allocator: *const AllocationCallbacks<'_>,
|
||||
p_device: *mut Device,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_enumerate_device_extension_properties"]
|
||||
pub fn enumerate_device_extension_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_layer_name: *const c_char,
|
||||
p_property_count: *mut u32,
|
||||
p_properties: *mut ExtensionProperties,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_enumerate_device_layer_properties"]
|
||||
pub fn enumerate_device_layer_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_property_count: *mut u32,
|
||||
p_properties: *mut LayerProperties,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_enumerate_physical_device_groups"]
|
||||
pub fn enumerate_physical_device_groups(
|
||||
instance: Instance,
|
||||
p_physical_device_group_count: *mut u32,
|
||||
p_physical_device_group_properties: *mut PhysicalDeviceGroupProperties<'_>,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_enumerate_physical_devices"]
|
||||
pub fn enumerate_physical_devices(
|
||||
instance: Instance,
|
||||
p_physical_device_count: *mut u32,
|
||||
p_physical_devices: *mut PhysicalDevice,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_get_device_proc_addr"]
|
||||
pub fn get_device_proc_addr(device: Device, p_name: *const c_char) -> PFN_vkVoidFunction;
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_external_buffer_properties"]
|
||||
pub fn get_physical_device_external_buffer_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_external_buffer_info: *const PhysicalDeviceExternalBufferInfo<'_>,
|
||||
p_external_buffer_properties: *mut ExternalBufferProperties<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_external_fence_properties"]
|
||||
pub fn get_physical_device_external_fence_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_external_fence_info: *const PhysicalDeviceExternalFenceInfo<'_>,
|
||||
p_external_fence_properties: *mut ExternalFenceProperties<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_external_semaphore_properties"]
|
||||
pub fn get_physical_device_external_semaphore_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_external_semaphore_info: *const PhysicalDeviceExternalSemaphoreInfo<'_>,
|
||||
p_external_semaphore_properties: *mut ExternalSemaphoreProperties<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_features"]
|
||||
pub fn get_physical_device_features(
|
||||
physical_device: PhysicalDevice,
|
||||
p_features: *mut PhysicalDeviceFeatures,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_features2"]
|
||||
pub fn get_physical_device_features2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_features: *mut PhysicalDeviceFeatures2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_format_properties"]
|
||||
pub fn get_physical_device_format_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
format: Format,
|
||||
p_format_properties: *mut FormatProperties,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_format_properties2"]
|
||||
pub fn get_physical_device_format_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
format: Format,
|
||||
p_format_properties: *mut FormatProperties2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_image_format_properties"]
|
||||
pub fn get_physical_device_image_format_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
format: Format,
|
||||
ty: ImageType,
|
||||
tiling: ImageTiling,
|
||||
usage: ImageUsageFlags,
|
||||
flags: ImageCreateFlags,
|
||||
p_image_format_properties: *mut ImageFormatProperties,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_image_format_properties2"]
|
||||
pub fn get_physical_device_image_format_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_image_format_info: *const PhysicalDeviceImageFormatInfo2<'_>,
|
||||
p_image_format_properties: *mut ImageFormatProperties2<'_>,
|
||||
) -> Result;
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_memory_properties"]
|
||||
pub fn get_physical_device_memory_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_memory_properties: *mut PhysicalDeviceMemoryProperties,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_memory_properties2"]
|
||||
pub fn get_physical_device_memory_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_memory_properties: *mut PhysicalDeviceMemoryProperties2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_properties"]
|
||||
pub fn get_physical_device_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_properties: *mut PhysicalDeviceProperties,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_properties2"]
|
||||
pub fn get_physical_device_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_properties: *mut PhysicalDeviceProperties2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_queue_family_properties"]
|
||||
pub fn get_physical_device_queue_family_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_queue_family_property_count: *mut u32,
|
||||
p_queue_family_properties: *mut QueueFamilyProperties,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_queue_family_properties2"]
|
||||
pub fn get_physical_device_queue_family_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_queue_family_property_count: *mut u32,
|
||||
p_queue_family_properties: *mut QueueFamilyProperties2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_sparse_image_format_properties"]
|
||||
pub fn get_physical_device_sparse_image_format_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
format: Format,
|
||||
ty: ImageType,
|
||||
samples: SampleCountFlags,
|
||||
usage: ImageUsageFlags,
|
||||
tiling: ImageTiling,
|
||||
p_property_count: *mut u32,
|
||||
p_properties: *mut SparseImageFormatProperties,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_sparse_image_format_properties2"]
|
||||
pub fn get_physical_device_sparse_image_format_properties2(
|
||||
physical_device: PhysicalDevice,
|
||||
p_format_info: *const PhysicalDeviceSparseImageFormatInfo2<'_>,
|
||||
p_property_count: *mut u32,
|
||||
p_properties: *mut SparseImageFormatProperties2<'_>,
|
||||
);
|
||||
|
||||
#[link_name = "vmm_vk_get_physical_device_tool_properties"]
|
||||
pub fn get_physical_device_tool_properties(
|
||||
physical_device: PhysicalDevice,
|
||||
p_tool_count: *mut u32,
|
||||
p_tool_properties: *mut PhysicalDeviceToolProperties<'_>,
|
||||
) -> Result;
|
||||
}
|
@ -1,28 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
use self::buffer::VulkanBuffer;
|
||||
use self::ffi::{
|
||||
create_device, enumerate_device_extension_properties, enumerate_device_layer_properties,
|
||||
enumerate_physical_device_groups, enumerate_physical_devices, get_device_proc_addr,
|
||||
get_physical_device_external_buffer_properties, get_physical_device_external_fence_properties,
|
||||
get_physical_device_external_semaphore_properties, get_physical_device_features,
|
||||
get_physical_device_features2, get_physical_device_format_properties,
|
||||
get_physical_device_format_properties2, get_physical_device_image_format_properties,
|
||||
get_physical_device_image_format_properties2, get_physical_device_memory_properties,
|
||||
get_physical_device_memory_properties2, get_physical_device_properties,
|
||||
get_physical_device_properties2, get_physical_device_queue_family_properties,
|
||||
get_physical_device_queue_family_properties2,
|
||||
get_physical_device_sparse_image_format_properties,
|
||||
get_physical_device_sparse_image_format_properties2, get_physical_device_tool_properties,
|
||||
};
|
||||
use super::{Screen, ScreenBuffer};
|
||||
use crate::vmm::VmmScreen;
|
||||
use ash::vk::{DeviceCreateInfo, DeviceQueueCreateInfo, Handle, QueueFlags};
|
||||
use ash::{Device, Instance, InstanceFnV1_0, InstanceFnV1_1, InstanceFnV1_3};
|
||||
use ash::Device;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
mod buffer;
|
||||
mod ffi;
|
||||
|
||||
/// Implementation of [`Screen`] using Vulkan.
|
||||
pub struct Vulkan {
|
||||
@ -31,44 +16,15 @@ pub struct Vulkan {
|
||||
}
|
||||
|
||||
impl Vulkan {
|
||||
pub fn new(screen: &VmmScreen) -> Result<Self, VulkanError> {
|
||||
// Wrap VkInstance.
|
||||
let instance = screen.vk_instance.try_into().unwrap();
|
||||
let instance = ash::vk::Instance::from_raw(instance);
|
||||
let instance = Instance::from_parts_1_3(
|
||||
instance,
|
||||
InstanceFnV1_0 {
|
||||
destroy_instance: Self::destroy_instance,
|
||||
enumerate_physical_devices,
|
||||
get_physical_device_features,
|
||||
get_physical_device_format_properties,
|
||||
get_physical_device_image_format_properties,
|
||||
get_physical_device_properties,
|
||||
get_physical_device_queue_family_properties,
|
||||
get_physical_device_memory_properties,
|
||||
get_device_proc_addr,
|
||||
create_device,
|
||||
enumerate_device_extension_properties,
|
||||
enumerate_device_layer_properties,
|
||||
get_physical_device_sparse_image_format_properties,
|
||||
},
|
||||
InstanceFnV1_1 {
|
||||
enumerate_physical_device_groups,
|
||||
get_physical_device_features2,
|
||||
get_physical_device_properties2,
|
||||
get_physical_device_format_properties2,
|
||||
get_physical_device_image_format_properties2,
|
||||
get_physical_device_queue_family_properties2,
|
||||
get_physical_device_memory_properties2,
|
||||
get_physical_device_sparse_image_format_properties2,
|
||||
get_physical_device_external_buffer_properties,
|
||||
get_physical_device_external_fence_properties,
|
||||
get_physical_device_external_semaphore_properties,
|
||||
},
|
||||
InstanceFnV1_3 {
|
||||
get_physical_device_tool_properties,
|
||||
},
|
||||
);
|
||||
pub fn from_screen(screen: &VmmScreen) -> Result<Self, VulkanError> {
|
||||
let entry = ash::Entry::linked();
|
||||
|
||||
let instance = unsafe {
|
||||
ash::Instance::load(
|
||||
entry.static_fn(),
|
||||
ash::vk::Instance::from_raw(screen.vk_instance.try_into().unwrap()),
|
||||
)
|
||||
};
|
||||
|
||||
// Wrap VkPhysicalDevice.
|
||||
let physical = screen.vk_device.try_into().unwrap();
|
||||
@ -76,32 +32,28 @@ impl Vulkan {
|
||||
|
||||
// Setup VkDeviceQueueCreateInfo.
|
||||
let queue = unsafe { instance.get_physical_device_queue_family_properties(physical) }
|
||||
.iter()
|
||||
.into_iter()
|
||||
.position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS))
|
||||
.unwrap();
|
||||
let queues = [DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue.try_into().unwrap())
|
||||
.queue_priorities(&[1.0])];
|
||||
.ok_or(VulkanError::NoQueue)?;
|
||||
|
||||
let queue = queue
|
||||
.try_into()
|
||||
.map_err(|_| VulkanError::QueueOutOfBounds(queue))?;
|
||||
|
||||
let queues = DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue)
|
||||
.queue_priorities(&[1.0]);
|
||||
|
||||
// Create logical device.
|
||||
let device = DeviceCreateInfo::default().queue_create_infos(&queues);
|
||||
let device = match unsafe { instance.create_device(physical, &device, None) } {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(VulkanError::CreateDeviceFailed(e)),
|
||||
};
|
||||
let device = DeviceCreateInfo::default().queue_create_infos(std::slice::from_ref(&queues));
|
||||
let device = unsafe { instance.create_device(physical, &device, None) }
|
||||
.map_err(VulkanError::CreateDeviceFailed)?;
|
||||
|
||||
Ok(Self {
|
||||
buffer: Arc::new(VulkanBuffer::new()),
|
||||
device,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "system" fn destroy_instance(
|
||||
_: ash::vk::Instance,
|
||||
_: *const ash::vk::AllocationCallbacks<'_>,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Vulkan {
|
||||
@ -127,6 +79,12 @@ impl Screen for Vulkan {
|
||||
/// Represents an error when [`Vulkan::new()`] fails.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VulkanError {
|
||||
#[error("couldn't find suitable queue")]
|
||||
NoQueue,
|
||||
|
||||
#[error("queue index #{0} out of bounds")]
|
||||
QueueOutOfBounds(usize),
|
||||
|
||||
#[error("couldn't create a logical device")]
|
||||
CreateDeviceFailed(#[source] ash::vk::Result),
|
||||
}
|
||||
|
2
gui/src/ui.rs
Normal file
2
gui/src/ui.rs
Normal file
@ -0,0 +1,2 @@
|
||||
// This macro includes the generated Rust code from .slint files
|
||||
slint::include_modules!();
|
139
gui/src/vmm/ffi.rs
Normal file
139
gui/src/vmm/ffi.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use super::{DebugResult, KernelStop, Vmm, VmmEvent, VmmScreen};
|
||||
use crate::debug::DebugClient;
|
||||
use crate::error::RustError;
|
||||
use crate::profile::Profile;
|
||||
use crate::screen::Screen;
|
||||
use gdbstub::common::Signal;
|
||||
use gdbstub::stub::state_machine::GdbStubStateMachine;
|
||||
use gdbstub::stub::MultiThreadStopReason;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_start(
|
||||
kernel: *const c_char,
|
||||
screen: *const VmmScreen,
|
||||
profile: *const Profile,
|
||||
debugger: *mut DebugClient,
|
||||
event: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
cx: *mut c_void,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Vmm {
|
||||
// Consume the debugger now to prevent memory leak in case of error.
|
||||
let debugger = if debugger.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(*Box::from_raw(debugger))
|
||||
};
|
||||
|
||||
// Check if path UTF-8.
|
||||
let path = match CStr::from_ptr(kernel).to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
*err = RustError::new("path of the kernel is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let screen = unsafe { &*screen };
|
||||
let profile = unsafe { &*profile };
|
||||
|
||||
match Vmm::new(path, screen, profile, debugger, event, cx) {
|
||||
Ok(vmm) => Box::into_raw(Box::new(vmm)),
|
||||
Err(e) => {
|
||||
*err = RustError::wrap(e).into_c();
|
||||
null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) {
|
||||
drop(Box::from_raw(vmm));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
|
||||
match (*vmm).screen.update() {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => RustError::wrap(e).into_c(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop) -> DebugResult {
|
||||
// Consume stop reason now to prevent memory leak.
|
||||
let vmm = &mut *vmm;
|
||||
let mut stop = if stop.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Box::from_raw(stop).0)
|
||||
};
|
||||
|
||||
loop {
|
||||
// Check current state.
|
||||
let r = match vmm.gdb.take().unwrap() {
|
||||
GdbStubStateMachine::Idle(s) => match super::debug::dispatch_idle(&mut vmm.cpu, s) {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(v)) => {
|
||||
// No pending data from the debugger.
|
||||
vmm.gdb = Some(v.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
GdbStubStateMachine::Running(s) => {
|
||||
match super::debug::dispatch_running(&mut vmm.cpu, s, stop.take()) {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(v)) => {
|
||||
// No pending data from the debugger.
|
||||
vmm.gdb = Some(v.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => {
|
||||
vmm.cpu.lock();
|
||||
|
||||
s.interrupt_handled(
|
||||
&mut vmm.cpu,
|
||||
Some(MultiThreadStopReason::Signal(Signal::SIGINT)),
|
||||
)
|
||||
.map_err(|e| RustError::with_source("couldn't handle CTRL+C from a debugger", e))
|
||||
}
|
||||
GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected,
|
||||
};
|
||||
|
||||
match r {
|
||||
Ok(v) => vmm.gdb = Some(v),
|
||||
Err(e) => return DebugResult::Error { reason: e.into_c() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_debug_socket(vmm: *mut Vmm) -> isize {
|
||||
let s = match &mut (*vmm).gdb {
|
||||
Some(v) => v,
|
||||
None => return -1,
|
||||
};
|
||||
|
||||
match s {
|
||||
GdbStubStateMachine::Idle(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Running(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Disconnected(s) => s.borrow_conn().socket() as _,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) {
|
||||
(*vmm).shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutting_down(vmm: *mut Vmm) -> bool {
|
||||
(*vmm).shutdown.load(Ordering::Relaxed)
|
||||
}
|
@ -24,6 +24,15 @@ pub type Default = self::os::Hvf;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub type Default = self::os::Whp;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub type HypervisorError = self::os::KvmError;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type HypervisorError = self::os::HvfError;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub type HypervisorError = self::os::WhpError;
|
||||
|
||||
/// Underlying hypervisor (e.g. KVM on Linux).
|
||||
pub trait Hypervisor: Send + Sync + 'static {
|
||||
type Mapper: RamMapper;
|
||||
|
@ -10,16 +10,17 @@ use crate::debug::DebugClient;
|
||||
use crate::error::RustError;
|
||||
use crate::profile::Profile;
|
||||
use crate::screen::Screen;
|
||||
use gdbstub::common::Signal;
|
||||
use cpu::GdbError;
|
||||
use gdbstub::stub::state_machine::GdbStubStateMachine;
|
||||
use gdbstub::stub::MultiThreadStopReason;
|
||||
use gdbstub::stub::{GdbStubError, MultiThreadStopReason};
|
||||
use kernel::{KernelError, ProgramHeaderError};
|
||||
use obconf::{BootEnv, ConsoleType, Vm};
|
||||
use std::cmp::max;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::io::Read;
|
||||
use std::num::NonZero;
|
||||
use std::ptr::null_mut;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@ -29,532 +30,13 @@ use thiserror::Error;
|
||||
mod arch;
|
||||
mod cpu;
|
||||
mod debug;
|
||||
#[cfg(feature = "qt")]
|
||||
mod ffi;
|
||||
mod hv;
|
||||
mod hw;
|
||||
mod kernel;
|
||||
mod ram;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_start(
|
||||
kernel: *const c_char,
|
||||
screen: *const VmmScreen,
|
||||
profile: *const Profile,
|
||||
debugger: *mut DebugClient,
|
||||
event: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
cx: *mut c_void,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Vmm {
|
||||
// Consume the debugger now to prevent memory leak in case of error.
|
||||
let debugger = match debugger.is_null() {
|
||||
true => None,
|
||||
false => Some(Box::from_raw(debugger)),
|
||||
};
|
||||
|
||||
// Check if path UTF-8.
|
||||
let path = match CStr::from_ptr(kernel).to_str() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
*err = RustError::new("path of the kernel is not UTF-8").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Open kernel image.
|
||||
let mut file = match Kernel::open(path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't open {path}"), e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Get program header enumerator.
|
||||
let hdrs = match file.program_headers() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(
|
||||
format_args!("couldn't start enumerating program headers of {path}"),
|
||||
e,
|
||||
)
|
||||
.into_c();
|
||||
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Parse program headers.
|
||||
let mut segments = Vec::new();
|
||||
let mut dynamic = None;
|
||||
let mut note = None;
|
||||
|
||||
for (index, item) in hdrs.enumerate() {
|
||||
// Check if success.
|
||||
let hdr = match item {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(
|
||||
format_args!("couldn't read program header #{index} on {path}"),
|
||||
e,
|
||||
)
|
||||
.into_c();
|
||||
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Process the header.
|
||||
match hdr.p_type {
|
||||
PT_LOAD => {
|
||||
if hdr.p_filesz > TryInto::<u64>::try_into(hdr.p_memsz).unwrap() {
|
||||
*err =
|
||||
RustError::new(format!("invalid p_filesz on on PT_LOAD {index}")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
segments.push(hdr);
|
||||
}
|
||||
PT_DYNAMIC => {
|
||||
if dynamic.is_some() {
|
||||
*err = RustError::new("multiple PT_DYNAMIC is not supported").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
dynamic = Some(hdr);
|
||||
}
|
||||
PT_NOTE => {
|
||||
if note.is_some() {
|
||||
*err = RustError::new("multiple PT_NOTE is not supported").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
note = Some(hdr);
|
||||
}
|
||||
PT_PHDR | PT_GNU_EH_FRAME | PT_GNU_STACK | PT_GNU_RELRO => {}
|
||||
v => {
|
||||
*err = RustError::new(format!("unknown p_type {v} on program header {index}"))
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segments.sort_unstable_by_key(|i| i.p_vaddr);
|
||||
|
||||
// Make sure the first PT_LOAD includes the ELF header.
|
||||
match segments.first() {
|
||||
Some(hdr) => {
|
||||
if hdr.p_offset != 0 {
|
||||
*err = RustError::new("the first PT_LOAD does not includes ELF header").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
*err = RustError::new("no any PT_LOAD on the kernel").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if PT_DYNAMIC exists.
|
||||
let dynamic = match dynamic {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
*err = RustError::new("no PT_DYNAMIC segment on the kernel").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Check if PT_NOTE exists.
|
||||
let note = match note {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
*err = RustError::new("no PT_NOTE segment on the kernel").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Seek to PT_NOTE.
|
||||
let mut data = match file.segment_data(¬e) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't seek to PT_NOTE on {path}"), e)
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Parse PT_NOTE.
|
||||
let mut vm_page_size = None;
|
||||
|
||||
for i in 0.. {
|
||||
// Check remaining data.
|
||||
if data.limit() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Read note header.
|
||||
let mut buf = [0u8; 4 * 3];
|
||||
|
||||
if let Err(e) = data.read_exact(&mut buf) {
|
||||
*err = RustError::with_source(format_args!("couldn't read kernel note #{i} header"), e)
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Parse note header.
|
||||
let nlen: usize = u32::from_ne_bytes(buf[..4].try_into().unwrap())
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let dlen: usize = u32::from_ne_bytes(buf[4..8].try_into().unwrap())
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let ty = u32::from_ne_bytes(buf[8..].try_into().unwrap());
|
||||
|
||||
if nlen > 0xff {
|
||||
*err = RustError::new(format!("name on kernel note #{i} is too large")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
if dlen > 0xff {
|
||||
*err = RustError::new(format!("description on kernel note #{i} is too large")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Read note name + description.
|
||||
let nalign = nlen.next_multiple_of(4);
|
||||
let mut buf = vec![0u8; nalign + dlen];
|
||||
|
||||
if let Err(e) = data.read_exact(&mut buf) {
|
||||
*err = RustError::with_source(format_args!("couldn't read kernel note #{i} data"), e)
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Check name.
|
||||
let name = match CStr::from_bytes_until_nul(&buf) {
|
||||
Ok(v) if v.to_bytes_with_nul().len() == nlen => v,
|
||||
_ => {
|
||||
*err = RustError::new(format!("kernel note #{i} has invalid name")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
if name.to_bytes() != b"obkrnl" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse description.
|
||||
match ty {
|
||||
0 => {
|
||||
if vm_page_size.is_some() {
|
||||
*err = RustError::new(format!("kernel note #{i} is duplicated")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
vm_page_size = buf[nalign..]
|
||||
.try_into()
|
||||
.map(usize::from_ne_bytes)
|
||||
.ok()
|
||||
.and_then(NonZero::new)
|
||||
.filter(|v| v.is_power_of_two());
|
||||
|
||||
if vm_page_size.is_none() {
|
||||
*err =
|
||||
RustError::new(format!("invalid description on kernel note #{i}")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
v => {
|
||||
*err = RustError::new(format!("unknown type {v} on kernel note #{i}")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if page size exists.
|
||||
let vm_page_size = match vm_page_size {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
*err = RustError::new("no page size in kernel note").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Get page size on the host.
|
||||
let host_page_size = match get_page_size() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't get host page size", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Get kernel memory size.
|
||||
let mut len = 0;
|
||||
|
||||
for hdr in &segments {
|
||||
if hdr.p_vaddr < len {
|
||||
*err = RustError::new(format!(
|
||||
"PT_LOAD at {:#x} is overlapped with the previous PT_LOAD",
|
||||
hdr.p_vaddr
|
||||
))
|
||||
.into_c();
|
||||
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
len = match hdr.p_vaddr.checked_add(hdr.p_memsz) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
*err = RustError::new(format!("invalid p_memsz on PT_LOAD at {:#x}", hdr.p_vaddr))
|
||||
.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Round kernel memory size.
|
||||
let block_size = max(vm_page_size, host_page_size);
|
||||
let len = match len {
|
||||
0 => {
|
||||
*err = RustError::new("the kernel has PT_LOAD with zero length").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
v => match v.checked_next_multiple_of(block_size.get()) {
|
||||
Some(v) => NonZero::new_unchecked(v),
|
||||
None => {
|
||||
*err = RustError::new("total size of PT_LOAD is too large").into_c();
|
||||
return null_mut();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Setup virtual devices.
|
||||
let ram = NonZero::new(1024 * 1024 * 1024 * 8).unwrap();
|
||||
let event = VmmEventHandler { fp: event, cx };
|
||||
let devices = Arc::new(setup_devices(ram.get(), block_size, event));
|
||||
|
||||
// Setup hypervisor.
|
||||
let mut hv = match self::hv::new(8, ram, block_size, debugger.is_some()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't setup a hypervisor", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Map the kernel.
|
||||
let feats = hv.cpu_features().clone();
|
||||
let mut ram = RamBuilder::new(hv.ram_mut());
|
||||
let kern = match ram.alloc_kernel(len) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't allocate RAM for the kernel", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
for hdr in &segments {
|
||||
// Seek to segment data.
|
||||
let mut data = match file.segment_data(hdr) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(
|
||||
format_args!("couldn't seek to offset {}", hdr.p_offset),
|
||||
e,
|
||||
)
|
||||
.into_c();
|
||||
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Read segment data.
|
||||
let mut seg = &mut kern[hdr.p_vaddr..(hdr.p_vaddr + hdr.p_memsz)];
|
||||
|
||||
match std::io::copy(&mut data, &mut seg) {
|
||||
Ok(v) => {
|
||||
if v != hdr.p_filesz {
|
||||
*err = RustError::new(format!("{path} is incomplete")).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(
|
||||
format_args!("couldn't read kernet at offset {}", hdr.p_offset),
|
||||
e,
|
||||
)
|
||||
.into_c();
|
||||
|
||||
return null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate stack.
|
||||
if let Err(e) = ram.alloc_stack(NonZero::new(1024 * 1024 * 2).unwrap()) {
|
||||
*err = RustError::with_source("couldn't allocate RAM for stack", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Allocate arguments.
|
||||
let env = BootEnv::Vm(Vm {
|
||||
vmm: devices.vmm().addr(),
|
||||
console: devices.console().addr(),
|
||||
host_page_size,
|
||||
});
|
||||
|
||||
if let Err(e) = ram.alloc_args(env, (*profile).kernel_config().clone()) {
|
||||
*err = RustError::with_source("couldn't allocate RAM for arguments", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
// Build RAM.
|
||||
let map = match ram.build(&feats, vm_page_size, &devices, dynamic) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't build RAM", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Setup screen.
|
||||
let screen = match crate::screen::Default::new(&*screen) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source("couldn't setup a screen", e).into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Setup CPU manager.
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let mut cpu = CpuManager::new(
|
||||
Arc::new(hv),
|
||||
screen.buffer().clone(),
|
||||
devices,
|
||||
event,
|
||||
shutdown.clone(),
|
||||
);
|
||||
|
||||
// Setup GDB stub.
|
||||
let gdb = match debugger
|
||||
.map(|client| {
|
||||
gdbstub::stub::GdbStub::new(*client)
|
||||
.run_state_machine(&mut cpu)
|
||||
.map_err(|e| RustError::with_source("couldn't setup a GDB stub", e))
|
||||
})
|
||||
.transpose()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = e.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn main CPU.
|
||||
cpu.spawn(map.kern_vaddr + file.entry(), Some(map), gdb.is_some());
|
||||
|
||||
// Create VMM.
|
||||
let vmm = Vmm {
|
||||
cpu,
|
||||
screen,
|
||||
gdb,
|
||||
shutdown,
|
||||
};
|
||||
|
||||
Box::into_raw(vmm.into())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) {
|
||||
drop(Box::from_raw(vmm));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
|
||||
match (*vmm).screen.update() {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => RustError::wrap(e).into_c(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop) -> DebugResult {
|
||||
// Consume stop reason now to prevent memory leak.
|
||||
let vmm = &mut *vmm;
|
||||
let mut stop = match stop.is_null() {
|
||||
true => None,
|
||||
false => Some(Box::from_raw(stop).0),
|
||||
};
|
||||
|
||||
loop {
|
||||
// Check current state.
|
||||
let r = match vmm.gdb.take().unwrap() {
|
||||
GdbStubStateMachine::Idle(s) => match self::debug::dispatch_idle(&mut vmm.cpu, s) {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(v)) => {
|
||||
// No pending data from the debugger.
|
||||
vmm.gdb = Some(v.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
GdbStubStateMachine::Running(s) => {
|
||||
match self::debug::dispatch_running(&mut vmm.cpu, s, stop.take()) {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(v)) => {
|
||||
// No pending data from the debugger.
|
||||
vmm.gdb = Some(v.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => {
|
||||
vmm.cpu.lock();
|
||||
|
||||
s.interrupt_handled(
|
||||
&mut vmm.cpu,
|
||||
Some(MultiThreadStopReason::Signal(Signal::SIGINT)),
|
||||
)
|
||||
.map_err(|e| RustError::with_source("couldn't handle CTRL+C from a debugger", e))
|
||||
}
|
||||
GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected,
|
||||
};
|
||||
|
||||
match r {
|
||||
Ok(v) => vmm.gdb = Some(v),
|
||||
Err(e) => return DebugResult::Error { reason: e.into_c() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_debug_socket(vmm: *mut Vmm) -> isize {
|
||||
let s = match &mut (*vmm).gdb {
|
||||
Some(v) => v,
|
||||
None => return -1,
|
||||
};
|
||||
|
||||
match s {
|
||||
GdbStubStateMachine::Idle(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Running(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Disconnected(s) => s.borrow_conn().socket() as _,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) {
|
||||
(*vmm).shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutting_down(vmm: *mut Vmm) -> bool {
|
||||
(*vmm).shutdown.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_page_size() -> Result<NonZero<usize>, std::io::Error> {
|
||||
let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) };
|
||||
@ -591,6 +73,283 @@ pub struct Vmm {
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Vmm {
|
||||
pub fn new(
|
||||
kernel_path: impl AsRef<Path>,
|
||||
screen: &VmmScreen,
|
||||
profile: &Profile,
|
||||
debugger: Option<DebugClient>,
|
||||
event_handler: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
cx: *mut c_void,
|
||||
) -> Result<Self, VmmError> {
|
||||
let path = kernel_path.as_ref();
|
||||
|
||||
// Open kernel image.
|
||||
let mut kernel_img =
|
||||
Kernel::open(path).map_err(|e| VmmError::OpenKernel(e, path.to_path_buf()))?;
|
||||
|
||||
// Get program header enumerator.
|
||||
let hdrs = kernel_img
|
||||
.program_headers()
|
||||
.map_err(|e| VmmError::EnumerateProgramHeaders(e, path.to_path_buf()))?;
|
||||
|
||||
// Parse program headers.
|
||||
let mut segments = Vec::new();
|
||||
let mut dynamic = None;
|
||||
let mut note = None;
|
||||
|
||||
for (index, item) in hdrs.enumerate() {
|
||||
// Check if success.
|
||||
let hdr =
|
||||
item.map_err(|e| VmmError::ReadProgramHeader(e, index, path.to_path_buf()))?;
|
||||
|
||||
// Process the header.
|
||||
match hdr.p_type {
|
||||
PT_LOAD => {
|
||||
if hdr.p_filesz > u64::try_from(hdr.p_memsz).unwrap() {
|
||||
return Err(VmmError::InvalidFilesz(index));
|
||||
}
|
||||
|
||||
segments.push(hdr);
|
||||
}
|
||||
PT_DYNAMIC => {
|
||||
if dynamic.is_some() {
|
||||
return Err(VmmError::MultipleDynamic);
|
||||
}
|
||||
|
||||
dynamic = Some(hdr);
|
||||
}
|
||||
PT_NOTE => {
|
||||
if note.is_some() {
|
||||
return Err(VmmError::MultipleNote);
|
||||
}
|
||||
|
||||
note = Some(hdr);
|
||||
}
|
||||
PT_PHDR | PT_GNU_EH_FRAME | PT_GNU_STACK | PT_GNU_RELRO => {}
|
||||
v => return Err(VmmError::UnknownProgramHeaderType(v, index)),
|
||||
}
|
||||
}
|
||||
|
||||
segments.sort_unstable_by_key(|i| i.p_vaddr);
|
||||
|
||||
// Make sure the first PT_LOAD includes the ELF header.
|
||||
let hdr = segments.first().ok_or(VmmError::NoLoadSegment)?;
|
||||
|
||||
if hdr.p_offset != 0 {
|
||||
return Err(VmmError::ElfHeaderNotInFirstLoadSegment);
|
||||
}
|
||||
|
||||
// Check if PT_DYNAMIC exists.
|
||||
let dynamic = dynamic.ok_or(VmmError::NoDynamicSegment)?;
|
||||
|
||||
// Check if PT_NOTE exists.
|
||||
let note = note.ok_or(VmmError::NoNoteSegment)?;
|
||||
|
||||
// Seek to PT_NOTE.
|
||||
let mut data: std::io::Take<&mut std::fs::File> = kernel_img
|
||||
.segment_data(¬e)
|
||||
.map_err(|e| VmmError::SeekToNote(e, path.to_path_buf()))?;
|
||||
|
||||
// Parse PT_NOTE.
|
||||
let mut vm_page_size = None;
|
||||
|
||||
for i in 0u32.. {
|
||||
// Check remaining data.
|
||||
if data.limit() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Read note header.
|
||||
let mut buf = [0u8; 4 * 3];
|
||||
|
||||
data.read_exact(&mut buf)
|
||||
.map_err(|e| VmmError::ReadKernelNote(e, i))?;
|
||||
|
||||
// Parse note header.
|
||||
let nlen: usize = u32::from_ne_bytes(buf[..4].try_into().unwrap())
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let dlen: usize = u32::from_ne_bytes(buf[4..8].try_into().unwrap())
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let ty = u32::from_ne_bytes(buf[8..].try_into().unwrap());
|
||||
|
||||
if nlen > 0xff {
|
||||
return Err(VmmError::NoteNameTooLarge(i));
|
||||
}
|
||||
|
||||
if dlen > 0xff {
|
||||
return Err(VmmError::InvalidNoteDescription(i));
|
||||
}
|
||||
|
||||
// Read note name + description.
|
||||
let nalign = nlen.next_multiple_of(4);
|
||||
let mut buf = vec![0u8; nalign + dlen];
|
||||
|
||||
data.read_exact(&mut buf)
|
||||
.map_err(|e| VmmError::ReadKernelNoteData(e, i))?;
|
||||
|
||||
// Check name.
|
||||
let name = match CStr::from_bytes_until_nul(&buf) {
|
||||
Ok(v) if v.to_bytes_with_nul().len() == nlen => v,
|
||||
_ => return Err(VmmError::InvalidNoteName(i)),
|
||||
};
|
||||
|
||||
if name.to_bytes() != b"obkrnl" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse description.
|
||||
match ty {
|
||||
0 => {
|
||||
if vm_page_size.is_some() {
|
||||
return Err(VmmError::DuplicateKernelNote(i));
|
||||
}
|
||||
vm_page_size = buf[nalign..]
|
||||
.try_into()
|
||||
.map(usize::from_ne_bytes)
|
||||
.ok()
|
||||
.and_then(NonZero::new)
|
||||
.filter(|v| v.is_power_of_two());
|
||||
|
||||
if vm_page_size.is_none() {
|
||||
return Err(VmmError::InvalidNoteDescription(i));
|
||||
}
|
||||
}
|
||||
v => return Err(VmmError::UnknownKernelNoteType(v, i)),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if page size exists.
|
||||
let vm_page_size = vm_page_size.ok_or(VmmError::NoPageSizeInKernelNote)?;
|
||||
|
||||
// Get page size on the host.
|
||||
let host_page_size = get_page_size().map_err(VmmError::GetHostPageSize)?;
|
||||
|
||||
// Get kernel memory size.
|
||||
let mut len = 0;
|
||||
|
||||
for hdr in &segments {
|
||||
if hdr.p_vaddr < len {
|
||||
return Err(VmmError::OverlappedLoadSegment(hdr.p_vaddr));
|
||||
}
|
||||
|
||||
len = hdr
|
||||
.p_vaddr
|
||||
.checked_add(hdr.p_memsz)
|
||||
.ok_or(VmmError::InvalidPmemsz(hdr.p_vaddr))?;
|
||||
}
|
||||
|
||||
// Round kernel memory size.
|
||||
let block_size = max(vm_page_size, host_page_size);
|
||||
let len = NonZero::new(len)
|
||||
.ok_or(VmmError::ZeroLengthLoadSegment)?
|
||||
.get()
|
||||
.checked_next_multiple_of(block_size.get())
|
||||
.ok_or(VmmError::TotalSizeTooLarge)?;
|
||||
|
||||
// Setup RAM.
|
||||
let ram_size = NonZero::new(1024 * 1024 * 1024 * 8).unwrap();
|
||||
|
||||
// Setup virtual devices.
|
||||
let event = VmmEventHandler {
|
||||
event: event_handler,
|
||||
cx,
|
||||
};
|
||||
let devices = Arc::new(setup_devices(ram_size.get(), block_size, event));
|
||||
|
||||
// Setup hypervisor.
|
||||
let mut hv = unsafe { self::hv::new(8, ram_size, block_size, debugger.is_some()) }
|
||||
.map_err(VmmError::SetupHypervisor)?;
|
||||
|
||||
// Map the kernel.
|
||||
let feats = hv.cpu_features().clone();
|
||||
let mut ram = RamBuilder::new(hv.ram_mut());
|
||||
|
||||
let kern = ram
|
||||
.alloc_kernel(NonZero::new(len).unwrap())
|
||||
.map_err(VmmError::AllocateRamForKernel)?;
|
||||
|
||||
for hdr in &segments {
|
||||
// Seek to segment data.
|
||||
let mut data = kernel_img
|
||||
.segment_data(hdr)
|
||||
.map_err(VmmError::SeekToOffset)?;
|
||||
|
||||
// Read segment data.
|
||||
let mut seg = &mut kern[hdr.p_vaddr..(hdr.p_vaddr + hdr.p_memsz)];
|
||||
|
||||
match std::io::copy(&mut data, &mut seg) {
|
||||
Ok(v) => {
|
||||
if v != hdr.p_filesz {
|
||||
return Err(VmmError::IncompleteKernel(path.to_path_buf()));
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(VmmError::ReadKernel(e, hdr.p_offset)),
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate stack.
|
||||
ram.alloc_stack(NonZero::new(1024 * 1024 * 2).unwrap())
|
||||
.map_err(VmmError::AllocateRamForStack)?;
|
||||
|
||||
// Allocate arguments.
|
||||
let env = BootEnv::Vm(Vm {
|
||||
vmm: devices.vmm().addr(),
|
||||
console: devices.console().addr(),
|
||||
host_page_size,
|
||||
});
|
||||
|
||||
ram.alloc_args(env, profile.kernel_config().clone())
|
||||
.map_err(VmmError::AllocateRamForArgs)?;
|
||||
|
||||
// Build RAM.
|
||||
let map = ram
|
||||
.build(&feats, vm_page_size, &devices, dynamic)
|
||||
.map_err(VmmError::BuildRam)?;
|
||||
|
||||
// Setup screen.
|
||||
let screen = crate::screen::Default::from_screen(screen).map_err(VmmError::SetupScreen)?;
|
||||
|
||||
// Setup CPU manager.
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let mut cpu_manager = CpuManager::new(
|
||||
Arc::new(hv),
|
||||
screen.buffer().clone(),
|
||||
devices,
|
||||
event,
|
||||
shutdown.clone(),
|
||||
);
|
||||
|
||||
// Setup GDB stub.
|
||||
let gdb = debugger
|
||||
.map(|client| {
|
||||
gdbstub::stub::GdbStub::new(client)
|
||||
.run_state_machine(&mut cpu_manager)
|
||||
.map_err(VmmError::SetupGdbStub)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// Spawn main CPU.
|
||||
cpu_manager.spawn(
|
||||
map.kern_vaddr + kernel_img.entry(),
|
||||
Some(map),
|
||||
gdb.is_some(),
|
||||
);
|
||||
|
||||
// Create VMM.
|
||||
let vmm = Vmm {
|
||||
cpu: cpu_manager,
|
||||
screen,
|
||||
gdb,
|
||||
shutdown,
|
||||
};
|
||||
|
||||
Ok(vmm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Vmm {
|
||||
fn drop(&mut self) {
|
||||
// Set shutdown flag before dropping the other fields so their background thread can stop
|
||||
@ -615,13 +374,13 @@ pub struct VmmScreen {
|
||||
/// Encapsulates a function to handle VMM events.
|
||||
#[derive(Clone, Copy)]
|
||||
struct VmmEventHandler {
|
||||
fp: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
event: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
cx: *mut c_void,
|
||||
}
|
||||
|
||||
impl VmmEventHandler {
|
||||
unsafe fn invoke(self, e: VmmEvent) {
|
||||
(self.fp)(&e, self.cx)
|
||||
(self.event)(&e, self.cx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,6 +443,116 @@ pub enum DebugResult {
|
||||
Error { reason: *mut RustError },
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VmmError {
|
||||
#[error("couldn't open kernel path {1}")]
|
||||
OpenKernel(#[source] KernelError, PathBuf),
|
||||
|
||||
#[error("couldn't start enumerating program headers of {1}")]
|
||||
EnumerateProgramHeaders(#[source] std::io::Error, PathBuf),
|
||||
|
||||
#[error("couldn't read program header #{1} on {2}")]
|
||||
ReadProgramHeader(#[source] ProgramHeaderError, usize, PathBuf),
|
||||
|
||||
#[error("invalid p_filesz on on PT_LOAD {0}")]
|
||||
InvalidFilesz(usize),
|
||||
|
||||
#[error("multiple PT_DYNAMIC is not supported")]
|
||||
MultipleDynamic,
|
||||
|
||||
#[error("multiple PT_NOTE is not supported")]
|
||||
MultipleNote,
|
||||
|
||||
#[error("unknown p_type {0} on program header {1}")]
|
||||
UnknownProgramHeaderType(u32, usize),
|
||||
|
||||
#[error("the first PT_LOAD does not include ELF header")]
|
||||
ElfHeaderNotInFirstLoadSegment,
|
||||
|
||||
#[error("no PT_LOAD on the kernel")]
|
||||
NoLoadSegment,
|
||||
|
||||
#[error("no PT_DYNAMIC on the kernel")]
|
||||
NoDynamicSegment,
|
||||
|
||||
#[error("no PT_NOTE on the kernel")]
|
||||
NoNoteSegment,
|
||||
|
||||
#[error("couldn't seek to PT_NOTE on {1}")]
|
||||
SeekToNote(#[source] std::io::Error, PathBuf),
|
||||
|
||||
#[error("couldn't read kernel note #{1}")]
|
||||
ReadKernelNote(#[source] std::io::Error, u32),
|
||||
|
||||
#[error("name on kernel note #{0} is too large")]
|
||||
NoteNameTooLarge(u32),
|
||||
|
||||
#[error("invalid description on kernel note #{0}")]
|
||||
InvalidNoteDescription(u32),
|
||||
|
||||
#[error("couldn't read kernel note #{1} data")]
|
||||
ReadKernelNoteData(#[source] std::io::Error, u32),
|
||||
|
||||
#[error("kernel note #{0} has invalid name")]
|
||||
InvalidNoteName(u32),
|
||||
|
||||
#[error("kernel note #{0} is duplicated")]
|
||||
DuplicateKernelNote(u32),
|
||||
|
||||
#[error("unknown type {0} on kernel note #{1}")]
|
||||
UnknownKernelNoteType(u32, u32),
|
||||
|
||||
#[error("no page size in kernel note")]
|
||||
NoPageSizeInKernelNote,
|
||||
|
||||
#[error("couldn't get host page size")]
|
||||
GetHostPageSize(#[source] std::io::Error),
|
||||
|
||||
#[error("PT_LOAD at {0:#} is overlapped with the previous PT_LOAD")]
|
||||
OverlappedLoadSegment(usize),
|
||||
|
||||
#[error("invalid p_memsz on PT_LOAD at {0:#}")]
|
||||
InvalidPmemsz(usize),
|
||||
|
||||
#[error("the kernel has PT_LOAD with zero length")]
|
||||
ZeroLengthLoadSegment,
|
||||
|
||||
#[error("total size of PT_LOAD is too large")]
|
||||
TotalSizeTooLarge,
|
||||
|
||||
#[error("couldn't setup a hypervisor")]
|
||||
SetupHypervisor(#[source] hv::HypervisorError),
|
||||
|
||||
#[error("couldn't allocate RAM for the kernel")]
|
||||
AllocateRamForKernel(#[source] hv::RamError),
|
||||
|
||||
#[error("couldn't seek to offset")]
|
||||
SeekToOffset(#[source] std::io::Error),
|
||||
|
||||
#[error("{0} is incomplete")]
|
||||
IncompleteKernel(PathBuf),
|
||||
|
||||
#[error("couldn't read kernel at offset {1}")]
|
||||
ReadKernel(#[source] std::io::Error, u64),
|
||||
|
||||
#[error("couldn't allocate RAM for stack")]
|
||||
AllocateRamForStack(#[source] hv::RamError),
|
||||
|
||||
#[error("couldn't allocate RAM for arguments")]
|
||||
AllocateRamForArgs(#[source] hv::RamError),
|
||||
|
||||
#[error("couldn't build RAM")]
|
||||
BuildRam(#[source] ram::RamBuilderError),
|
||||
|
||||
#[error("couldn't setup a screen")]
|
||||
SetupScreen(#[source] crate::screen::ScreenError),
|
||||
|
||||
#[error("couldn't setup a GDB stub")]
|
||||
SetupGdbStub(
|
||||
#[source] GdbStubError<GdbError, <DebugClient as gdbstub::conn::Connection>::Error>,
|
||||
),
|
||||
}
|
||||
|
||||
/// Represents an error when [`main_cpu()`] fails to reach event loop.
|
||||
#[derive(Debug, Error)]
|
||||
enum MainCpuError {
|
||||
|
248
gui/vulkan.cpp
248
gui/vulkan.cpp
@ -3,251 +3,3 @@
|
||||
#include <QVulkanFunctions>
|
||||
|
||||
QVulkanFunctions *vkFunctions;
|
||||
|
||||
extern "C" VkResult vmm_vk_enumerate_physical_devices(
|
||||
VkInstance instance,
|
||||
uint32_t *p_physical_device_count,
|
||||
VkPhysicalDevice *p_physical_devices)
|
||||
{
|
||||
return vkFunctions->vkEnumeratePhysicalDevices(
|
||||
instance,
|
||||
p_physical_device_count,
|
||||
p_physical_devices);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_features(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceFeatures *p_features)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceFeatures(physical_device, p_features);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_format_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkFormat format,
|
||||
VkFormatProperties *p_format_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceFormatProperties(physical_device, format, p_format_properties);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_get_physical_device_image_format_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkFormat format,
|
||||
VkImageType ty,
|
||||
VkImageTiling tiling,
|
||||
VkImageUsageFlags usage,
|
||||
VkImageCreateFlags flags,
|
||||
VkImageFormatProperties *p_image_format_properties)
|
||||
{
|
||||
return vkFunctions->vkGetPhysicalDeviceImageFormatProperties(
|
||||
physical_device,
|
||||
format,
|
||||
ty,
|
||||
tiling,
|
||||
usage,
|
||||
flags,
|
||||
p_image_format_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceProperties *p_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceProperties(physical_device, p_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_queue_family_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
uint32_t *p_queue_family_property_count,
|
||||
VkQueueFamilyProperties *p_queue_family_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||
physical_device,
|
||||
p_queue_family_property_count,
|
||||
p_queue_family_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_memory_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceMemoryProperties *p_memory_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceMemoryProperties(physical_device, p_memory_properties);
|
||||
}
|
||||
|
||||
extern "C" PFN_vkVoidFunction vmm_vk_get_device_proc_addr(VkDevice device, const char *p_name)
|
||||
{
|
||||
return vkFunctions->vkGetDeviceProcAddr(device, p_name);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_create_device(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkDeviceCreateInfo *p_create_info,
|
||||
const VkAllocationCallbacks *p_allocator,
|
||||
VkDevice *p_device)
|
||||
{
|
||||
return vkFunctions->vkCreateDevice(physical_device, p_create_info, p_allocator, p_device);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_enumerate_device_extension_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
const char *p_layer_name,
|
||||
uint32_t *p_property_count,
|
||||
VkExtensionProperties *p_properties)
|
||||
{
|
||||
return vkFunctions->vkEnumerateDeviceExtensionProperties(
|
||||
physical_device,
|
||||
p_layer_name,
|
||||
p_property_count,
|
||||
p_properties);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_enumerate_device_layer_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
uint32_t *p_property_count,
|
||||
VkLayerProperties *p_properties)
|
||||
{
|
||||
return vkFunctions->vkEnumerateDeviceLayerProperties(
|
||||
physical_device,
|
||||
p_property_count,
|
||||
p_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_sparse_image_format_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkFormat format,
|
||||
VkImageType ty,
|
||||
VkSampleCountFlagBits samples,
|
||||
VkImageUsageFlags usage,
|
||||
VkImageTiling tiling,
|
||||
uint32_t *p_property_count,
|
||||
VkSparseImageFormatProperties *p_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceSparseImageFormatProperties(
|
||||
physical_device,
|
||||
format,
|
||||
ty,
|
||||
samples,
|
||||
usage,
|
||||
tiling,
|
||||
p_property_count,
|
||||
p_properties);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_enumerate_physical_device_groups(
|
||||
VkInstance instance,
|
||||
uint32_t *p_physical_device_group_count,
|
||||
VkPhysicalDeviceGroupProperties *p_physical_device_group_properties)
|
||||
{
|
||||
return vkFunctions->vkEnumeratePhysicalDeviceGroups(
|
||||
instance,
|
||||
p_physical_device_group_count,
|
||||
p_physical_device_group_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_features2(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceFeatures2 *p_features)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceFeatures2(physical_device, p_features);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceProperties2 *p_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceProperties2(physical_device, p_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_format_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkFormat format,
|
||||
VkFormatProperties2 *p_format_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceFormatProperties2(physical_device, format, p_format_properties);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_get_physical_device_image_format_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceImageFormatInfo2 *p_image_format_info,
|
||||
VkImageFormatProperties2 *p_image_format_properties)
|
||||
{
|
||||
return vkFunctions->vkGetPhysicalDeviceImageFormatProperties2(
|
||||
physical_device,
|
||||
p_image_format_info,
|
||||
p_image_format_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_queue_family_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
uint32_t *p_queue_family_property_count,
|
||||
VkQueueFamilyProperties2 *p_queue_family_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties2(
|
||||
physical_device,
|
||||
p_queue_family_property_count,
|
||||
p_queue_family_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_memory_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
VkPhysicalDeviceMemoryProperties2 *p_memory_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceMemoryProperties2(physical_device, p_memory_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_sparse_image_format_properties2(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceSparseImageFormatInfo2 *p_format_info,
|
||||
uint32_t *p_property_count,
|
||||
VkSparseImageFormatProperties2 *p_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceSparseImageFormatProperties2(
|
||||
physical_device,
|
||||
p_format_info,
|
||||
p_property_count,
|
||||
p_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_external_buffer_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceExternalBufferInfo *p_external_buffer_info,
|
||||
VkExternalBufferProperties *p_external_buffer_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceExternalBufferProperties(
|
||||
physical_device,
|
||||
p_external_buffer_info,
|
||||
p_external_buffer_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_external_fence_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceExternalFenceInfo *p_external_fence_info,
|
||||
VkExternalFenceProperties *p_external_fence_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceExternalFenceProperties(
|
||||
physical_device,
|
||||
p_external_fence_info,
|
||||
p_external_fence_properties);
|
||||
}
|
||||
|
||||
extern "C" void vmm_vk_get_physical_device_external_semaphore_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
const VkPhysicalDeviceExternalSemaphoreInfo *p_external_semaphore_info,
|
||||
VkExternalSemaphoreProperties *p_external_semaphore_properties)
|
||||
{
|
||||
vkFunctions->vkGetPhysicalDeviceExternalSemaphoreProperties(
|
||||
physical_device,
|
||||
p_external_semaphore_info,
|
||||
p_external_semaphore_properties);
|
||||
}
|
||||
|
||||
extern "C" VkResult vmm_vk_get_physical_device_tool_properties(
|
||||
VkPhysicalDevice physical_device,
|
||||
uint32_t *p_tool_count,
|
||||
VkPhysicalDeviceToolProperties *p_tool_properties)
|
||||
{
|
||||
return vkFunctions->vkGetPhysicalDeviceToolProperties(
|
||||
physical_device,
|
||||
p_tool_count,
|
||||
p_tool_properties);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
use byteorder::{ByteOrder, BE, LE};
|
||||
use std::fs::File;
|
||||
use std::io::{ErrorKind, Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A loaded param.sfo.
|
||||
@ -16,6 +18,12 @@ pub struct Param {
|
||||
}
|
||||
|
||||
impl Param {
|
||||
pub fn open(path: impl AsRef<Path>) -> Result<Self, OpenError> {
|
||||
let file = File::open(path).map_err(OpenError::OpenFailed)?;
|
||||
|
||||
Self::read(file).map_err(OpenError::ReadFailed)
|
||||
}
|
||||
|
||||
pub fn read<R: Read + Seek>(mut raw: R) -> Result<Self, ReadError> {
|
||||
// Seek to the beginning.
|
||||
if let Err(e) = raw.seek(SeekFrom::Start(0)) {
|
||||
@ -238,6 +246,15 @@ impl Param {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OpenError {
|
||||
#[error("couldn't open the file")]
|
||||
OpenFailed(#[source] std::io::Error),
|
||||
|
||||
#[error("couldn't read the file")]
|
||||
ReadFailed(#[source] ReadError),
|
||||
}
|
||||
|
||||
/// Errors for reading param.sfo.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ReadError {
|
||||
|
Loading…
Reference in New Issue
Block a user