diff --git a/CMakeLists.txt b/CMakeLists.txt index 13bc7d23475..08caaf07eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,7 @@ endif() add_custom_target(all_generated) add_custom_target(run - COMMAND "${CMAKE_COMMAND}" -E env "SERENITY_ARCH=${SERENITY_ARCH}" "${SerenityOS_SOURCE_DIR}/Meta/run.sh" + COMMAND "${CMAKE_COMMAND}" -E env "SERENITY_SOURCE_DIR=${SerenityOS_SOURCE_DIR}" "SERENITY_ARCH=${SERENITY_ARCH}" "${SerenityOS_SOURCE_DIR}/Meta/run.sh" USES_TERMINAL ) diff --git a/Meta/run.py b/Meta/run.py new file mode 100755 index 00000000000..e354ac932e0 --- /dev/null +++ b/Meta/run.py @@ -0,0 +1,908 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2023, the SerenityOS developers. +# Copyright (c) 2023, kleines Filmröllchen +# +# SPDX-License-Identifier: BSD-2-Clause + +from __future__ import annotations + +import os +import re +import sys +from dataclasses import dataclass, field +from enum import Enum, unique +from itertools import chain, repeat +from os import access, environ +from pathlib import Path +from shutil import which +from subprocess import run +from typing import Any, Callable, Literal + +QEMU_MINIMUM_REQUIRED_MAJOR_VERSION = 6 +QEMU_MINIMUM_REQUIRED_MINOR_VERSION = 2 + + +class RunError(Exception): + pass + + +@unique +class Arch(Enum): + """SerenityOS architecture, not host architecture.""" + + Aarch64 = "aarch64" + x86_64 = "x86_64" + + +@unique +class QEMUKind(Enum): + """VM distinctions determining which hardware acceleration technology *might* be used.""" + + # Linux and anything that may or may not have KVM, including WSL with Linux QEMU. + Other = "kvm" + # WSL with native Windows QEMU (and possibly WHPX). + NativeWindows = "whpx" + # MacOS with HVF. + MacOS = "hvf" + # Serenity on Serenity with ported QEMU + SerenityOS = "tcg" + + +@unique +class MachineType(Enum): + Default = "" + QEMUTap = "qtap" + QEMUWithoutNetwork = "qn" + QEMUExtLinux = "qextlinux" + QEMU35 = "q35" + QEMU35Grub = "q35grub" + QEMUGrub = "qgrub" + CI = "ci" + Limine = "limine" + Bochs = "b" + MicroVM = "microvm" + ISAPC = "isapc" + + def uses_grub(self) -> bool: + return self in [MachineType.QEMU35Grub, MachineType.QEMUGrub] + + def is_q35(self) -> bool: + return self in [MachineType.QEMU35Grub, MachineType.QEMU35] + + def supports_pc_speaker(self) -> bool: + """Whether the pcspk-audiodev option is allowed for this machine type.""" + return self in [ + MachineType.Default, + MachineType.QEMUTap, + MachineType.QEMUWithoutNetwork, + MachineType.QEMUExtLinux, + MachineType.QEMU35, + MachineType.QEMU35Grub, + MachineType.QEMUGrub, + MachineType.Limine, + ] + + +@dataclass +class Configuration: + """Run configuration, populated from command-line or environment variable data.""" + + # ## Programs and environmental configuration + virtualization_support: bool = False + bochs_binary: Path = Path("bochs") + qemu_binary: Path | None = None + qemu_kind: QEMUKind | None = None + kvm_usable: bool | None = None + architecture: Arch | None = None + serenity_src: Path | None = None + + # ## High-level run configuration + machine_type: MachineType = MachineType.Default + enable_gdb: bool = False + enable_gl: bool = False + # FIXME: Replace these three flags by a boot drive enum, see FIXME for boot_drive below. + nvme_enable: bool = True + sd_enable: bool = False + usb_boot_enable: bool = False + screen_count: int = 1 + host_ip: str = "127.0.0.1" + ethernet_device_type: str = "e1000" + disk_image: Path = Path("_disk_image") + + # ## Low-level QEMU configuration + # QEMU -append + kernel_cmdline: list[str] = field(default_factory=lambda: ["hello"]) + # QEMU -m + ram_size: str | None = "1G" + # QEMU -cpu + qemu_cpu: str | None = "max" + # QEMU -smp + cpu_count: int | None = 2 + # QEMU -machine + qemu_machine: str | None = None + # QEMU -usb + enable_usb: bool = False + # QEMU -audiodev + audio_backend: str | None = "none,id=snd0" + # Each is a QEMU -device related to audio. + audio_devices: list[str] = field(default_factory=list) + # QEMU -vga + vga_type: str | None = "none" + # QEMU -display; if None, will omit the option and let QEMU figure out which backend to use on its own. + display_backend: str | None = None + # A QEMU -device for the graphics card. + display_device: str | None = "VGA,vgamem_mb=64" + # QEMU -netdev + network_backend: str | None = None + # A QEMU -device for networking. + # Note that often, there are other network devices in the generic device list, added by specific machine types. + network_default_device: str | None = None + # QEMU -drive + # FIXME: Make an enum for the various boot drive options to handle boot drive selection more cleanly. + boot_drive: str | None = None + # Each is a QEMU -chardev + character_devices: list[str] = field(default_factory=list) + # Each is a QEMU -device + devices: list[str] = field(default_factory=list) + + # ## Argument lists and methods generating them + + # Argument list pertaining to Kernel and Prekernel image(s) + kernel_and_initrd_arguments: list[str] = field(default_factory=list) + # Argument list provided by the user for performing packet logging + packet_logging_arguments: list[str] = field(default_factory=list) + # Various arguments relating to SPICE setup + spice_arguments: list[str] = field(default_factory=list) + # Arbitrary extra arguments + extra_arguments: list[str] = field(default_factory=list) + + @staticmethod + def arguments_generator(prefix: str) -> Any: + """ + Construct an argument generator that returns some prefix and the member value(s) if the member value + is not None, or returns an empty list otherwise. + The member value is the return value of the function decorated with this decorator. + If a default is provided, in this case we return [prefix, default] instead. + Many of our configurable QEMU arguments work like this. + """ + + def decorate_function(member_accessor: Callable[[Configuration], str | list[str]]): + def generate_arguments(self: Configuration) -> list[str]: + member_value = member_accessor(self) + if member_value is not None: + if type(member_value) is list: + # apply the prefix to every element of the list + return list(chain(*zip(repeat(prefix), member_value))) + # NOTE: the typechecker gets confused and can't figure out that + # type(member_value) is *always* str here. + elif type(member_value) is str: + return [prefix, member_value] + return [] + + return generate_arguments + + return decorate_function + + @property + @arguments_generator(prefix="-accel") + def accelerator_arguments(self) -> str | None: + return self.qemu_kind.value if self.virtualization_support and (self.qemu_kind is not None) else "tcg" + + @property + def kernel_cmdline_arguments(self) -> list[str]: + return ["-append", " ".join(self.kernel_cmdline)] if len(self.kernel_cmdline) != 0 else [] + + @property + @arguments_generator(prefix="-m") + def ram_arguments(self) -> str | None: + return self.ram_size + + @property + @arguments_generator(prefix="-cpu") + def cpu_arguments(self) -> str | None: + return self.qemu_cpu + + @property + @arguments_generator(prefix="-smp") + def smp_arguments(self) -> str | None: + return str(self.cpu_count) if self.cpu_count is not None else None + + @property + @arguments_generator(prefix="-machine") + def machine_arguments(self) -> str | None: + return self.qemu_machine + + @property + def usb_arguments(self) -> list[str]: + return ["-usb"] if self.enable_usb else [] + + @property + @arguments_generator(prefix="-audiodev") + def audio_backend_arguments(self) -> str | None: + return self.audio_backend + + @property + @arguments_generator(prefix="-device") + def audio_devices_arguments(self) -> list[str] | None: + return self.audio_devices + + @property + @arguments_generator(prefix="-vga") + def vga_arguments(self) -> str | None: + return self.vga_type + + @property + @arguments_generator(prefix="-display") + def display_backend_arguments(self) -> str | None: + return self.display_backend + + @property + @arguments_generator(prefix="-device") + def display_device_arguments(self) -> str | None: + return self.display_device + + @property + def network_backend_arguments(self) -> list[str]: + return ["-netdev", self.network_backend] if self.network_backend is not None else ["-nic", "none"] + + @property + @arguments_generator(prefix="-device") + def network_default_arguments(self) -> str | None: + return self.network_default_device + + @property + @arguments_generator(prefix="-drive") + def boot_drive_arguments(self) -> str | None: + return self.boot_drive + + @property + @arguments_generator(prefix="-chardev") + def character_device_arguments(self) -> list[str]: + return self.character_devices + + @property + @arguments_generator(prefix="-device") + def device_arguments(self) -> list[str]: + return self.devices + + def add_device(self, device: str): + self.devices.append(device) + + def add_devices(self, devices: list[str]): + self.devices.extend(devices) + + +def kvm_usable() -> bool: + return access("/dev/kvm", os.R_OK | os.W_OK) + + +def determine_qemu_kind() -> QEMUKind: + if which("wslpath") is not None and environ.get("SERENITY_NATIVE_WINDOWS_QEMU", "1") == "1": + # Assume native Windows QEMU for now, + # we might discard that assuption later when we properly + # look for the binary. + return QEMUKind.NativeWindows + if sys.platform == "darwin": + return QEMUKind.MacOS + if os.uname().sysname == "SerenityOS": + return QEMUKind.SerenityOS + return QEMUKind.Other + + +def determine_serenity_arch() -> Arch: + match environ.get("SERENITY_ARCH"): + case "aarch64": + return Arch.Aarch64 + case "x86_64": + return Arch.x86_64 + case _: + raise RunError("Please specify a valid SerenityOS architecture") + + +def determine_machine_type() -> MachineType: + provided_machine_type = environ.get("SERENITY_RUN") + if provided_machine_type is not None: + try: + value = MachineType(provided_machine_type) + except ValueError: + raise RunError(f"{provided_machine_type} is not a valid SerenityOS machine type") + return value + return MachineType.Default + + +def detect_bochs() -> Path: + return Path(environ.get("SERENITY_BOCHS_BIN", "bochs")) + + +def detect_ram_size() -> str | None: + return environ.get("SERENITY_RAM_SIZE", "1G") + + +def setup_qemu_binary(config: Configuration): + qemu_binary_basename: str | None = None + if "SERENITY_QEMU_BIN" in environ: + qemu_binary_basename = environ.get("SERENITY_QEMU_BIN") + else: + match config.architecture: + case Arch.Aarch64: + qemu_binary_basename = "qemu-system-aarch64" + case Arch.x86_64: + qemu_binary_basename = "qemu-system-x86_64" + if qemu_binary_basename is None: + raise RunError("QEMU binary could not be determined") + + # Try finding native Windows QEMU first + if config.qemu_kind == QEMUKind.NativeWindows: + # FIXME: Consider using the wslwinreg module instead to access the registry more conveniently. + # Some Windows systems don't have reg.exe's directory on the PATH by default. + environ["PATH"] = environ["PATH"] + ":/mnt/c/Windows/System32" + try: + qemu_install_dir_result = run( + ["reg.exe", "query", r"HKLM\Software\QEMU", "/v", "Install_Dir", "/t", "REG_SZ"], + capture_output=True, + ) + if qemu_install_dir_result.returncode == 0: + registry_regex = re.compile(rb"Install_Dir\s+REG_SZ\s+(.*)$", flags=re.MULTILINE) + qemu_install_dir_match = registry_regex.search(qemu_install_dir_result.stdout) + if qemu_install_dir_match is not None: + # If Windows prints non-ASCII characters, those will most likely not be UTF-8. + # Therefore, don't decode sooner. Also, remove trailing '\r' + qemu_install_dir = Path(qemu_install_dir_match.group(1).decode("utf-8").strip()) + config.qemu_binary = Path( + run( + ["wslpath", "--", Path(qemu_install_dir, qemu_binary_basename)], + encoding="utf-8", + capture_output=True, + ).stdout.strip() + ).with_suffix(".exe") + # No native Windows QEMU, reconfigure to Linux QEMU without KVM + else: + config.virtualization_support = False + config.qemu_kind = QEMUKind.Other + except Exception: + # reg.exe not found; errors in reg.exe itself do not throw an error. + config.qemu_kind = QEMUKind.Other + + if config.qemu_binary is None: + # Setup full path for the binary if possible (otherwise trust system PATH) + local_qemu_bin = Path(str(config.serenity_src), "Toolchain/Local/qemu/bin/", qemu_binary_basename) + old_local_qemu_bin = Path(str(config.serenity_src), "Toolchain/Local/x86_64/bin/", qemu_binary_basename) + if local_qemu_bin.exists(): + config.qemu_binary = local_qemu_bin + elif old_local_qemu_bin.exists(): + config.qemu_binary = old_local_qemu_bin + else: + config.qemu_binary = Path(qemu_binary_basename) + + +def check_qemu_version(config: Configuration): + if config.qemu_binary is None: + raise RunError( + f"Please install QEMU version {QEMU_MINIMUM_REQUIRED_MAJOR_VERSION}.{QEMU_MINIMUM_REQUIRED_MINOR_VERSION} or newer or use the Toolchain/BuildQemu.sh script." # noqa: E501 + ) + version_information = run([config.qemu_binary, "-version"], capture_output=True, encoding="utf-8").stdout + qemu_version_regex = re.compile(r"QEMU emulator version ([1-9][0-9]*|0)\.([1-9][0-9]*|0)") + version_groups = qemu_version_regex.search(version_information) + if version_groups is None: + raise RunError(f'QEMU seems to be defective, its version information is "{version_information}"') + major = int(version_groups.group(1)) + minor = int(version_groups.group(2)) + + if major < QEMU_MINIMUM_REQUIRED_MAJOR_VERSION or ( + major == QEMU_MINIMUM_REQUIRED_MAJOR_VERSION and minor < QEMU_MINIMUM_REQUIRED_MINOR_VERSION + ): + raise RunError( + f"Required QEMU >= {QEMU_MINIMUM_REQUIRED_MAJOR_VERSION}.{QEMU_MINIMUM_REQUIRED_MINOR_VERSION}!\ + Found {major}.{minor}. Please install a newer version of QEMU or use the Toolchain/BuildQemu.sh script." + ) + + +def setup_virtualization_support(config: Configuration): + provided_virtualization_enable = environ.get("SERENITY_VIRTUALIZATION_SUPPORT") + # The user config always forces the platform-appropriate virtualizer to be used, + # even if we couldn't detect it otherwise; this is intended behavior. + if provided_virtualization_enable is not None: + config.virtualization_support = provided_virtualization_enable == "1" + else: + if (config.kvm_usable and config.architecture != Arch.Aarch64 and os.uname().machine != Arch.Aarch64.value) or ( + config.qemu_kind == QEMUKind.NativeWindows and config.architecture != Arch.Aarch64 + ): + config.virtualization_support = True + + if config.virtualization_support: + available_accelerators = run( + [str(config.qemu_binary), "-accel", "help"], + capture_output=True, + ).stdout + # Check if HVF is actually available if we're on MacOS + if config.qemu_kind == QEMUKind.MacOS and (b"hvf" not in available_accelerators): + config.virtualization_support = False + + +def setup_basic_kernel_cmdline(config: Configuration): + provided_cmdline = environ.get("SERENITY_KERNEL_CMDLINE") + if provided_cmdline is not None: + # Split environment variable at spaces, since we don't pass arguments like shell scripts do. + config.kernel_cmdline.extend(provided_cmdline.split(sep=None)) + + # Handle system-specific arguments now, boot type specific arguments are handled later. + if config.qemu_kind == QEMUKind.NativeWindows: + config.kernel_cmdline.append("disable_virtio") + + +def setup_disk_image_path(config: Configuration): + provided_disk_image = environ.get("SERENITY_DISK_IMAGE") + if provided_disk_image is not None: + config.disk_image = Path(provided_disk_image) + else: + if config.machine_type.uses_grub(): + config.disk_image = Path("grub_disk_image") + elif config.machine_type == MachineType.Limine: + config.disk_image = Path("limine_disk_image") + elif config.machine_type == MachineType.QEMUExtLinux: + config.disk_image = Path("extlinux_disk_image") + + if config.qemu_kind == QEMUKind.NativeWindows: + config.disk_image = Path( + run(["wslpath", "-w", config.disk_image], capture_output=True, encoding="utf-8").stdout.strip() + ) + + +def setup_cpu(config: Configuration): + if config.qemu_kind == QEMUKind.NativeWindows: + config.qemu_cpu = "max,vmx=off" + else: + provided_cpu = environ.get("SERENITY_QEMU_CPU") + if provided_cpu is not None: + config.qemu_cpu = provided_cpu + + +def setup_cpu_count(config: Configuration): + if config.architecture == Arch.Aarch64: + return + + provided_cpu_count = environ.get("SERENITY_CPUS") + if provided_cpu_count is not None: + try: + config.cpu_count = int(provided_cpu_count) + except ValueError: + raise RunError(f"Non-integer CPU count {provided_cpu_count}") + + if config.cpu_count is not None and config.qemu_cpu is not None and config.cpu_count <= 8: + # -x2apic is not a flag, but disables x2APIC for easier testing on lower CPU counts. + config.qemu_cpu += ",-x2apic" + + +def setup_spice(config: Configuration): + if environ.get("SERENITY_SPICE") == "1": + chardev_info = run( + [str(config.qemu_binary), "-chardev", "help"], + capture_output=True, + encoding="utf-8", + ).stdout.lower() + if "qemu-vdagent" in chardev_info: + config.spice_arguments = [ + "-chardev", + "qemu-vdagent,clipboard=on,mouse=off,id=vdagent,name=vdagent", + ] + elif "spicevmc" in chardev_info: + config.spice_arguments = ["-chardev", "spicevmc,id=vdagent,name=vdagent"] + else: + raise RunError("No compatible SPICE character device was found") + + if "spice" in chardev_info: + config.spice_arguments.extend(["-spice", "port=5930,agent-mouse=off,disable-ticketing=on"]) + if "spice" in chardev_info or "vdagent" in chardev_info: + config.spice_arguments.extend(["-device", "virtserialport,chardev=vdagent,nr=1"]) + + +def setup_audio_backend(config: Configuration): + if config.qemu_kind == QEMUKind.MacOS: + config.audio_backend = "coreaudio" + elif config.qemu_kind == QEMUKind.NativeWindows: + config.audio_backend = "dsound,timer-period=2000" + elif config.machine_type != MachineType.CI: + # FIXME: Use "-audiodev help" once that contains information on all our supported versions, + # "-audio-help" is marked as deprecated. + qemu_audio_help = run( + [str(config.qemu_binary), "-audio-help"], + capture_output=True, + encoding="utf-8", + ).stdout + if "-audiodev id=sdl" in qemu_audio_help: + config.audio_backend = "sdl" + elif "-audiodev id=pa" in qemu_audio_help: + config.audio_backend = "pa,timer-period=2000" + + if config.audio_backend is not None: + config.audio_backend += ",id=snd0" + + +def setup_audio_hardware(config: Configuration): + provided_audio_hardware = environ.get("SERENITY_AUDIO_HARDWARE", "intelhda") + match provided_audio_hardware: + case "ac97": + config.audio_devices = ["ac97,audiodev=snd0"] + case "intelhda": + config.audio_devices = ["ich9-intel-hda", "hda-output,audiodev=snd0"] + case _: + raise RunError(f"Unknown audio hardware {provided_audio_hardware}. Supported values: ac97, intelhda") + + if config.machine_type.supports_pc_speaker() and config.architecture == Arch.x86_64: + config.extra_arguments.extend(["-machine", "pcspk-audiodev=snd0"]) + + +def has_virgl() -> bool: + ldconfig_result = run(["ldconfig", "-p"], capture_output=True, encoding="utf-8").stdout.lower() + return "virglrenderer" in ldconfig_result + + +def setup_screens(config: Configuration): + provided_screen_count_unparsed = environ.get("SERENITY_SCREENS", "1") + try: + config.screen_count = int(provided_screen_count_unparsed) + except ValueError: + raise RunError(f"Invalid screen count {provided_screen_count_unparsed}") + + provided_display_backend = environ.get("SERENITY_QEMU_DISPLAY_BACKEND") + if provided_display_backend is not None: + config.display_backend = provided_display_backend + else: + qemu_display_info = run( + [str(config.qemu_binary), "-display", "help"], + capture_output=True, + encoding="utf-8", + ).stdout.lower() + if len(config.spice_arguments) != 0: + config.display_backend = "spice-app" + elif config.qemu_kind == QEMUKind.NativeWindows: + # QEMU for windows does not like gl=on, so detect if we are building in wsl, and if so, disable it + # Also, when using the GTK backend we run into this problem: + # https://github.com/SerenityOS/serenity/issues/7657 + config.display_backend = "sdl,gl=off" + elif config.screen_count > 1 and "sdl" in qemu_display_info: + config.display_backend = "sdl,gl=off" + elif "sdl" in qemu_display_info and has_virgl(): + config.display_backend = "sdl,gl=on" + elif "cocoa" in qemu_display_info: + config.display_backend = "cocoa,gl=off" + elif config.qemu_kind == QEMUKind.SerenityOS: + config.display_backend = "sdl,gl=off" + else: + config.display_backend = "gtk,gl=off" + + +def setup_display_device(config: Configuration): + config.enable_gl = environ.get("SERENITY_GL") == "1" + provided_display_device = environ.get("SERENITY_QEMU_DISPLAY_DEVICE") + if provided_display_device is not None: + config.display_device = provided_display_device + elif config.enable_gl: + # QEMU appears to not support the GL backend for VirtIO GPU variant on macOS. + if config.qemu_kind == QEMUKind.MacOS: + raise RunError("SERENITY_GL is not supported since there's no GL backend on macOS") + elif config.screen_count > 1: + raise RunError("SERENITY_GL and multi-monitor support cannot be setup simultaneously") + config.display_device = "virtio-vga-gl" + + elif config.screen_count > 1: + # QEMU appears to not support the virtio-vga VirtIO GPU variant on macOS. + # To ensure we can still boot on macOS with VirtIO GPU, use the virtio-gpu-pci + # variant, which lacks any VGA compatibility (which is not relevant for us anyway). + if config.qemu_kind == QEMUKind.MacOS: + config.display_device = f"virtio-gpu-pci,max_outputs={config.screen_count}" + else: + config.display_device = f"virtio-vga,max_outputs={config.screen_count}" + + # QEMU appears to always relay absolute mouse coordinates relative to the screen that the mouse is + # pointed to, without any way for us to know what screen it was. So, when dealing with multiple + # displays force using relative coordinates only. + config.kernel_cmdline.append("vmmouse=off") + + +def setup_boot_drive(config: Configuration): + provided_nvme_enable = environ.get("SERENITY_NVME_ENABLE") + if provided_nvme_enable is not None: + config.nvme_enable = provided_nvme_enable == "1" + provided_usb_boot_enable = environ.get("SERENITY_USE_SDCARD") + if provided_usb_boot_enable is not None: + config.sd_enable = provided_usb_boot_enable == "1" + provided_usb_boot_enable = environ.get("SERENITY_USE_USBDRIVE") + if provided_usb_boot_enable is not None: + config.usb_boot_enable = provided_usb_boot_enable == "1" + + if config.machine_type in [MachineType.MicroVM, MachineType.ISAPC]: + if config.nvme_enable: + print("Warning: NVMe does not work under MicroVM/ISA PC, automatically disabling it.") + config.nvme_enable = False + + if config.architecture == Arch.Aarch64: + config.boot_drive = f"file={config.disk_image},if=sd,format=raw,id=disk" + elif config.nvme_enable: + config.boot_drive = f"file={config.disk_image},format=raw,index=0,media=disk,if=none,id=disk" + config.add_devices( + [ + "i82801b11-bridge,id=bridge4", + "nvme,serial=deadbeef,drive=disk,bus=bridge4,logical_block_size=4096,physical_block_size=4096", + ] + ) + config.kernel_cmdline.append("root=nvme0:1:0") + elif config.sd_enable: + config.boot_drive = f"id=sd-boot-drive,if=none,format=raw,file={config.disk_image}" + config.add_devices(["sdhci-pci", "sd-card,drive=sd-boot-drive"]) + config.kernel_cmdline.append("root=sd2:0:0") + elif config.usb_boot_enable: + config.boot_drive = f"if=none,id=usbstick,format=raw,file={config.disk_image}" + config.add_device("usb-storage,drive=usbstick") + # FIXME: Find a better way to address the usb drive + config.kernel_cmdline.append("root=block3:0") + else: + config.boot_drive = f"file={config.disk_image},format=raw,index=0,media=disk,id=disk" + + +def determine_host_address() -> str: + return environ.get("SERENITY_HOST_IP", "127.0.0.1") + + +def setup_gdb(config: Configuration): + config.enable_gdb = environ.get("SERENITY_DISABLE_GDB_SOCKET") != "1" + if config.qemu_kind == QEMUKind.NativeWindows or ( + config.virtualization_support and config.qemu_kind == QEMUKind.MacOS + ): + config.enable_gdb = False + + if config.enable_gdb: + config.extra_arguments.extend(["-gdb", f"tcp:{config.host_ip}:1234"]) + + +def setup_network_hardware(config: Configuration): + config.packet_logging_arguments = (environ.get("SERENITY_PACKET_LOGGING_ARG", "")).split() + + provided_ethernet_device_type = environ.get("SERENITY_ETHERNET_DEVICE_TYPE") + if provided_ethernet_device_type is not None: + config.ethernet_device_type = provided_ethernet_device_type + + if config.architecture == Arch.Aarch64: + config.network_backend = None + config.network_default_device = None + else: + config.network_backend = f"user,id=breh,hostfwd=tcp:{config.host_ip}:8888-10.0.2.15:8888,\ +hostfwd=tcp:{config.host_ip}:8823-10.0.2.15:23,\ +hostfwd=tcp:{config.host_ip}:8000-10.0.2.15:8000,\ +hostfwd=tcp:{config.host_ip}:2222-10.0.2.15:22" + config.network_default_device = f"{config.ethernet_device_type},netdev=breh" + + +def setup_kernel(config: Configuration): + if config.architecture == Arch.Aarch64: + config.kernel_and_initrd_arguments = ["-kernel", "Kernel/Kernel"] + else: + config.kernel_and_initrd_arguments = ["-kernel", "Kernel/Prekernel/Prekernel", "-initrd", "Kernel/Kernel"] + + +def setup_machine_devices(config: Configuration): + # TODO: Maybe disable SPICE everwhere except the default machine? + + # Architecture specifics. + if config.architecture == Arch.Aarch64: + config.qemu_machine = "raspi3b" + config.cpu_count = None + config.vga_type = None + config.display_device = None + if config.machine_type != MachineType.CI: + # FIXME: Windows QEMU crashes when we set the same display as usual here. + config.display_backend = None + config.audio_devices = [] + config.extra_arguments.extend(["-serial", "stdio"]) + config.qemu_cpu = None + return + + # Machine specific base setups + match config.machine_type: + case MachineType.QEMU35Grub | MachineType.QEMU35: + config.qemu_machine = "q35" + config.vga_type = None + # We set up our own custom display devices. + config.display_device = None + config.add_devices( + [ + "vmware-svga", + "ich9-usb-ehci1,bus=pcie.0,multifunction=on,addr=0x5.0x0", + "ich9-usb-ehci2,bus=pcie.0,addr=0x5.0x2", + "ich9-usb-uhci1,bus=pcie.0,multifunction=on,addr=0x7.0x0", + "ich9-usb-uhci2,bus=pcie.0,addr=0x7.0x1", + "ich9-usb-uhci3,bus=pcie.0,addr=0x7.0x2", + "ich9-usb-uhci4,bus=pcie.0,addr=0x7.0x3", + "ich9-usb-uhci5,bus=pcie.0,addr=0x7.0x4", + "ich9-usb-uhci6,bus=pcie.0,addr=0x7.0x5", + "pcie-root-port,port=0x10,chassis=1,id=pcie.1,bus=pcie.0,multifunction=on,addr=0x6", + "pcie-root-port,port=0x11,chassis=2,id=pcie.2,bus=pcie.0,addr=0x6.0x1", + "pcie-root-port,port=0x12,chassis=3,id=pcie.3,bus=pcie.0,addr=0x6.0x2", + "pcie-root-port,port=0x13,chassis=4,id=pcie.4,bus=pcie.0,addr=0x6.0x3", + "pcie-root-port,port=0x14,chassis=5,id=pcie.5,bus=pcie.0,addr=0x6.0x4", + "pcie-root-port,port=0x15,chassis=6,id=pcie.6,bus=pcie.0,addr=0x6.0x5", + "pcie-root-port,port=0x16,chassis=7,id=pcie.7,bus=pcie.0,addr=0x6.0x6", + "pcie-root-port,port=0x17,chassis=8,id=pcie.8,bus=pcie.0,addr=0x6.0x7", + "ich9-intel-hda,bus=pcie.2,addr=0x03.0x0", + "bochs-display", + "nec-usb-xhci,bus=pcie.2,addr=0x11.0x0", + "pci-bridge,chassis_nr=1,id=bridge1,bus=pcie.4,addr=0x3.0x0", + "sdhci-pci,bus=bridge1,addr=0x1.0x0", + ] + ) + config.enable_usb = True + case MachineType.MicroVM | MachineType.ISAPC: + config.character_devices.append("stdio,id=stdout,mux=on") + config.qemu_cpu = "qemu64" + config.cpu_count = None + config.display_device = None + config.network_default_device = None + config.audio_devices = [] + config.add_devices(["isa-debugcon,chardev=stdout", "isa-vga", "ne2k_isa,netdev=breh"]) + + if config.machine_type == MachineType.MicroVM: + config.qemu_machine = "microvm,pit=on,rtc=on,pic=on" + config.add_devices(["isa-ide", "ide-hd,drive=disk", "i8042"]) + else: # ISAPC + config.qemu_machine = "isapc" + + case MachineType.CI: + config.display_backend = "none" + config.audio_backend = None + config.audio_devices = [] + config.extra_arguments.extend(["-serial", "stdio", "-no-reboot", "-monitor", "none"]) + config.spice_arguments = [] + if config.architecture == Arch.Aarch64: + config.extra_arguments.extend(["-serial", "file:debug.log"]) + else: + config.add_device("ich9-ahci") + config.extra_arguments.extend(["-debugcon", "file:debug.log"]) + + case _: + # Default machine + config.network_default_device = f"{config.network_default_device},bus=bridge1" + config.add_devices( + [ + "virtio-serial,max_ports=2", + "virtconsole,chardev=stdout", + "isa-debugcon,chardev=stdout", + "virtio-rng-pci", + "pci-bridge,chassis_nr=1,id=bridge1", + "i82801b11-bridge,bus=bridge1,id=bridge2", + "sdhci-pci,bus=bridge2", + "i82801b11-bridge,id=bridge3", + "sdhci-pci,bus=bridge3", + "ich9-ahci,bus=bridge3", + ] + ) + config.character_devices.append("stdio,id=stdout,mux=on") + config.enable_usb = True + + # Modifications for machine types that are *mostly* like the default, + # but not entirely (especially in terms of networking). + match config.machine_type: + case MachineType.QEMUWithoutNetwork | MachineType.QEMU35Grub: + config.network_backend = None + config.network_default_device = config.ethernet_device_type + config.packet_logging_arguments = [] + case MachineType.QEMUTap: + config.network_backend = "tap,ifname=tap0,id=br0" + config.network_default_device = f"{config.ethernet_device_type},netdev=br0" + case MachineType.QEMUGrub | MachineType.QEMUExtLinux: + config.kernel_cmdline = [] + + if config.qemu_kind != QEMUKind.NativeWindows: + config.extra_arguments.extend(["-qmp", "unix:qmp-sock,server,nowait"]) + + config.extra_arguments.extend(["-name", "SerenityOS", "-d", "guest_errors"]) + + +def assemble_arguments(config: Configuration) -> list[str | Path]: + if config.machine_type == MachineType.Bochs: + boch_src = Path(config.serenity_src or ".", "Meta/bochsrc") + return [config.bochs_binary, "-q", "-f", boch_src] + + return [ + config.qemu_binary or "", + # Deviate from standard order here: + # The device list contains PCI bridges which must be available for other devices. + *config.device_arguments, + *config.kernel_and_initrd_arguments, + *config.packet_logging_arguments, + *config.spice_arguments, + *config.extra_arguments, + *config.accelerator_arguments, + *config.kernel_cmdline_arguments, + *config.ram_arguments, + *config.cpu_arguments, + *config.smp_arguments, + *config.machine_arguments, + *config.usb_arguments, + *config.audio_backend_arguments, + *config.audio_devices_arguments, + *config.vga_arguments, + *config.display_backend_arguments, + *config.display_device_arguments, + *config.network_backend_arguments, + *config.network_default_arguments, + *config.boot_drive_arguments, + *config.character_device_arguments, + ] + + +class TapController: + """Context manager for setting up and tearing down a tap device when QEMU is run with tap networking.""" + + def __init__(self, machine_type: MachineType): + self.should_enable_tap = machine_type == MachineType.QEMUTap + + def __enter__(self) -> None: + if self.should_enable_tap: + run(["sudo", "ip", "tuntap", "del", "dev", "tap0", "mode", "tap"]) + user = os.getuid() + run(["sudo", "ip", "tuntap", "add", "dev", "tap0", "mode", "tap", "user", str(user)]) + + def __exit__(self, exc_type: type | None, exc_value: Any | None, traceback: Any | None) -> Literal[False]: + if self.should_enable_tap: + run(["sudo", "ip", "tuntap", "del", "dev", "tap0", "mode", "tap"]) + # Re-raise exceptions in any case. + return False + + +def configure_and_run(): + config = Configuration() + config.kvm_usable = kvm_usable() + config.qemu_kind = determine_qemu_kind() + config.architecture = determine_serenity_arch() + config.machine_type = determine_machine_type() + config.bochs_binary = detect_bochs() + config.ram_size = detect_ram_size() + config.host_ip = determine_host_address() + + serenity_src = environ.get("SERENITY_SOURCE_DIR") + if serenity_src is None: + raise RunError("SERENITY_SOURCE_DIR not set or empty") + config.serenity_src = Path(serenity_src) + + setup_qemu_binary(config) + check_qemu_version(config) + setup_virtualization_support(config) + setup_basic_kernel_cmdline(config) + setup_disk_image_path(config) + setup_cpu(config) + setup_cpu_count(config) + setup_spice(config) + setup_audio_backend(config) + setup_audio_hardware(config) + setup_screens(config) + setup_display_device(config) + setup_boot_drive(config) + setup_gdb(config) + setup_network_hardware(config) + setup_kernel(config) + setup_machine_devices(config) + + arguments = assemble_arguments(config) + + build_directory = environ.get("SERENITY_BUILD", ".") + os.chdir(build_directory) + + with TapController(config.machine_type): + run(arguments) + + +def main(): + try: + configure_and_run() + except KeyboardInterrupt: + pass + except RunError as e: + print(f"Error: {e}") + except Exception as e: + print(f"Unknown error: {e}") + print("This is likely a bug, consider filing a bug report.") + + +if __name__ == "__main__": + main() diff --git a/Meta/run.sh b/Meta/run.sh index 4e0523e04c4..62863a4a544 100755 --- a/Meta/run.sh +++ b/Meta/run.sh @@ -1,558 +1,13 @@ #!/bin/sh -# shellcheck disable=SC2086 # FIXME: fix these globing warnings set -e -die() { - echo "die: $*" - exit 1 -} - SCRIPT_DIR="$(dirname "${0}")" # https://www.shellcheck.net/wiki/SC1090 No need to shellcheck private config. # shellcheck source=/dev/null [ -x "$SCRIPT_DIR/../run-local.sh" ] && . "$SCRIPT_DIR/../run-local.sh" -#SERENITY_PACKET_LOGGING_ARG="-object filter-dump,id=hue,netdev=breh,file=e1000.pcap" +#export SERENITY_PACKET_LOGGING_ARG="-object filter-dump,id=hue,netdev=breh,file=e1000.pcap" -# FIXME: Enable for SERENITY_ARCH=aarch64 if on an aarch64 host? - -# Check if SERENITY_VIRTUALIZATION_SUPPORT is unset -if [ -z ${SERENITY_VIRTUALIZATION_SUPPORT+x} ]; then - VIRTUALIZATION_SUPPORT="0" - [ -e /dev/kvm ] && [ -r /dev/kvm ] && [ -w /dev/kvm ] && [ "$SERENITY_ARCH" != "aarch64" ] && [ "$(uname -m)" != "aarch64" ] && VIRTUALIZATION_SUPPORT="1" - command -v wslpath >/dev/null && VIRTUALIZATION_SUPPORT="1" -else - VIRTUALIZATION_SUPPORT="$SERENITY_VIRTUALIZATION_SUPPORT" -fi - -[ -z "$SERENITY_BOCHS_BIN" ] && SERENITY_BOCHS_BIN="bochs" - -# To support virtualization acceleration on mac -# we need to use 64-bit qemu -if [ "$(uname)" = "Darwin" ]; then - - if [ "$SERENITY_ARCH" != "aarch64" ]; then - [ -z "$SERENITY_QEMU_BIN" ] && SERENITY_QEMU_BIN="qemu-system-x86_64" - else - [ -z "$SERENITY_QEMU_BIN" ] && SERENITY_QEMU_BIN="qemu-system-aarch64" - fi - - if [ "$(uname -m)" = "x86_64" ]; then - if $SERENITY_QEMU_BIN --accel help | grep -q hvf; then - SERENITY_VIRT_TECH_ARG="--accel hvf" - fi - fi -fi - -# Prepend the toolchain qemu directory so we pick up QEMU from there -PATH="$SCRIPT_DIR/../Toolchain/Local/qemu/bin:$PATH" - -# Also prepend the x86_64 toolchain directory because that's where most -# people will have their QEMU binaries if they built them before the -# directory was changed to Toolchain/Local/qemu. -PATH="$SCRIPT_DIR/../Toolchain/Local/x86_64/bin:$PATH" - -SERENITY_RUN="${SERENITY_RUN:-$1}" - -if [ -z "$SERENITY_QEMU_BIN" ]; then - if command -v wslpath >/dev/null; then - # Some Windows systems don't have reg.exe's directory on the PATH by default. - PATH=$PATH:/mnt/c/Windows/System32 - QEMU_INSTALL_DIR=$(reg.exe query 'HKLM\Software\QEMU' /v Install_Dir /t REG_SZ | grep '^ Install_Dir' | sed 's/ / /g' | cut -f4- -d' ') - if [ -z "$QEMU_INSTALL_DIR" ]; then - if [ "$VIRTUALIZATION_SUPPORT" -eq "0" ]; then - die "Could not determine where QEMU for Windows is installed. Please make sure QEMU is installed or set SERENITY_QEMU_BIN if it is already installed." - fi - else - QEMU_BINARY_PREFIX="$(wslpath -- "${QEMU_INSTALL_DIR}" | tr -d '\r\n')/" - QEMU_BINARY_SUFFIX=".exe" - fi - fi - if [ "$SERENITY_ARCH" = "aarch64" ]; then - SERENITY_QEMU_BIN="${QEMU_BINARY_PREFIX}qemu-system-aarch64${QEMU_BINARY_SUFFIX}" - elif [ "$SERENITY_ARCH" = "x86_64" ]; then - SERENITY_QEMU_BIN="${QEMU_BINARY_PREFIX}qemu-system-x86_64${QEMU_BINARY_SUFFIX}" - else - die "Please specify a valid CPU architecture." - fi -fi - - -# For default values, see Kernel/CommandLine.cpp -[ -z "$SERENITY_KERNEL_CMDLINE" ] && SERENITY_KERNEL_CMDLINE="hello" - -[ -z "$SERENITY_RAM_SIZE" ] && SERENITY_RAM_SIZE=1G - -[ -z "$SERENITY_DISK_IMAGE" ] && { - if [ "$SERENITY_RUN" = q35grub ] || [ "$SERENITY_RUN" = qgrub ]; then - SERENITY_DISK_IMAGE="grub_disk_image" - elif [ "$SERENITY_RUN" = limine ]; then - SERENITY_DISK_IMAGE="limine_disk_image" - elif [ "$SERENITY_RUN" = qextlinux ]; then - SERENITY_DISK_IMAGE="extlinux_disk_image" - else - SERENITY_DISK_IMAGE="_disk_image" - fi - if command -v wslpath >/dev/null; then - case "$SERENITY_QEMU_BIN" in - /mnt/?/*) - SERENITY_DISK_IMAGE=$(wslpath -w "$SERENITY_DISK_IMAGE") - ;; - esac - fi -} - -SERENITY_QEMU_MIN_REQ_MAJOR_VERSION=6 -SERENITY_QEMU_MIN_REQ_MINOR_VERSION=2 -SERENITY_QEMU_MIN_REQ_VERSION="$SERENITY_QEMU_MIN_REQ_MAJOR_VERSION.$SERENITY_QEMU_MIN_REQ_MINOR_VERSION" -if ! command -v "$SERENITY_QEMU_BIN" >/dev/null 2>&1 ; then - die "Please install QEMU version $SERENITY_QEMU_MIN_REQ_VERSION or newer or use the Toolchain/BuildQemu.sh script." -fi - -installed_major_version=$("$SERENITY_QEMU_BIN" -version | head -n 1 | sed -E 's/QEMU emulator version ([1-9][0-9]*|0).*/\1/') -installed_minor_version=$("$SERENITY_QEMU_BIN" -version | head -n 1 | sed -E 's/QEMU emulator version [0-9]+\.([1-9][0-9]*|0).*/\1/') -if [ "$installed_major_version" -lt "$SERENITY_QEMU_MIN_REQ_MAJOR_VERSION" ] || - { [ "$installed_major_version" -eq "$SERENITY_QEMU_MIN_REQ_MAJOR_VERSION" ] && - [ "$installed_minor_version" -lt "$SERENITY_QEMU_MIN_REQ_MINOR_VERSION" ]; }; then - echo "Required QEMU >= $SERENITY_QEMU_MIN_REQ_VERSION! Found $($SERENITY_QEMU_BIN -version | head -n 1)" - echo "Please install a newer version of QEMU or use the Toolchain/BuildQemu.sh script." - die -fi - -NATIVE_WINDOWS_QEMU="0" - -if command -v wslpath >/dev/null; then - case "$SERENITY_QEMU_BIN" in - /mnt/?/*) - if [ -z "$SERENITY_VIRT_TECH_ARG" ]; then - if [ "$VIRTUALIZATION_SUPPORT" -eq "1" ]; then - if [ "$installed_major_version" -gt 5 ]; then - SERENITY_VIRT_TECH_ARG="-accel whpx,kernel-irqchip=off" - else - SERENITY_VIRT_TECH_ARG="-accel whpx" - fi - fi - SERENITY_VIRT_TECH_ARG="$SERENITY_VIRT_TECH_ARG -accel tcg" - fi - [ -z "$SERENITY_QEMU_CPU" ] && SERENITY_QEMU_CPU="max,vmx=off" - SERENITY_KERNEL_CMDLINE="$SERENITY_KERNEL_CMDLINE disable_virtio" - NATIVE_WINDOWS_QEMU="1" - ;; - esac -fi - -[ "$VIRTUALIZATION_SUPPORT" -eq "1" ] && [ "$NATIVE_WINDOWS_QEMU" -ne "1" ] && SERENITY_VIRT_TECH_ARG="-enable-kvm" - -[ -z "$SERENITY_QEMU_CPU" ] && SERENITY_QEMU_CPU="max" - -if [ "$SERENITY_ARCH" != "aarch64" ]; then - [ -z "$SERENITY_CPUS" ] && SERENITY_CPUS="2" - if [ "$SERENITY_CPUS" -le 8 ]; then - # Explicitly disable x2APIC so we can test it more easily - SERENITY_QEMU_CPU="$SERENITY_QEMU_CPU,-x2apic" - fi - - if [ -z "$SERENITY_SPICE" ] && "${SERENITY_QEMU_BIN}" -chardev help | grep -iq qemu-vdagent; then - SERENITY_SPICE_SERVER_CHARDEV="-chardev qemu-vdagent,clipboard=on,mouse=off,id=vdagent,name=vdagent" - elif "${SERENITY_QEMU_BIN}" -chardev help | grep -iq spicevmc; then - SERENITY_SPICE_SERVER_CHARDEV="-chardev spicevmc,id=vdagent,name=vdagent" - fi -fi - -if [ "$(uname)" = "Darwin" ]; then - SERENITY_AUDIO_BACKEND="-audiodev coreaudio,id=snd0" -elif [ "$NATIVE_WINDOWS_QEMU" -eq "1" ]; then - SERENITY_AUDIO_BACKEND="-audiodev dsound,id=snd0,timer-period=2000" -elif "$SERENITY_QEMU_BIN" -audio-help 2>&1 | grep -- "-audiodev id=sdl" >/dev/null; then - SERENITY_AUDIO_BACKEND="-audiodev sdl,id=snd0" -else - SERENITY_AUDIO_BACKEND="-audiodev pa,timer-period=2000,id=snd0" -fi - -SERENITY_AUDIO_HARDWARE="${SERENITY_AUDIO_HARDWARE:-intelhda}" -if [ "${SERENITY_AUDIO_HARDWARE}" = 'ac97' ]; then - SERENITY_AUDIO_DEVICE='-device ac97,audiodev=snd0' -elif [ "${SERENITY_AUDIO_HARDWARE}" = 'intelhda' ]; then - SERENITY_AUDIO_DEVICE='-device ich9-intel-hda -device hda-output,audiodev=snd0' -else - echo "Unknown audio hardware: ${SERENITY_AUDIO_HARDWARE}" - echo 'Supported values: ac97, intelhda' - exit 1 -fi - -if [ "$installed_major_version" -eq 5 ] && [ "$installed_minor_version" -eq 0 ]; then - SERENITY_AUDIO_PC_SPEAKER="-soundhw pcspk" -else - SERENITY_AUDIO_PC_SPEAKER="-machine pcspk-audiodev=snd0" -fi - -SERENITY_SCREENS="${SERENITY_SCREENS:-1}" -if [ "$SERENITY_SPICE" ]; then - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-spice-app}" -elif [ "$NATIVE_WINDOWS_QEMU" -eq "1" ]; then - # QEMU for windows does not like gl=on, so detect if we are building in wsl, and if so, disable it - # Also, when using the GTK backend we run into this problem: https://github.com/SerenityOS/serenity/issues/7657 - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-sdl,gl=off}" -elif [ $SERENITY_SCREENS -gt 1 ] && "${SERENITY_QEMU_BIN}" --display help | grep -iq sdl; then - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-sdl,gl=off}" -elif ! command -v wslpath >/dev/null && ("${SERENITY_QEMU_BIN}" --display help | grep -iq sdl) && (ldconfig -p | grep -iq virglrenderer); then - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-sdl,gl=on}" -elif "${SERENITY_QEMU_BIN}" --display help | grep -iq cocoa; then - # QEMU for OSX seems to only support cocoa - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-cocoa,gl=off}" -elif [ "$(uname -s)" = "SerenityOS" ]; then - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-sdl,gl=off}" -else - SERENITY_QEMU_DISPLAY_BACKEND="${SERENITY_QEMU_DISPLAY_BACKEND:-gtk,gl=off}" -fi - -SERENITY_GL="${SERENITY_GL:-0}" -if [ -z "$SERENITY_QEMU_DISPLAY_DEVICE" ]; then - if [ "$SERENITY_GL" = "1" ]; then - # QEMU appears to not support the GL backend for VirtIO GPU variant on macOS. - if [ "$(uname)" = "Darwin" ]; then - die "SERENITY_GL is not supported since there's no GL backend on macOS" - else - SERENITY_QEMU_DISPLAY_DEVICE="virtio-vga-gl " - fi - - if [ "$SERENITY_SCREENS" -gt 1 ]; then - die "SERENITY_GL and multi-monitor support cannot be setup simultaneously" - fi - elif [ "$SERENITY_SCREENS" -gt 1 ]; then - # QEMU appears to not support the virtio-vga VirtIO GPU variant on macOS. - # To ensure we can still boot on macOS with VirtIO GPU, use the virtio-gpu-pci - # variant, which lacks any VGA compatibility (which is not relevant for us anyway). - if [ "$(uname)" = "Darwin" ]; then - SERENITY_QEMU_DISPLAY_DEVICE="virtio-gpu-pci,max_outputs=$SERENITY_SCREENS " - else - SERENITY_QEMU_DISPLAY_DEVICE="virtio-vga,max_outputs=$SERENITY_SCREENS " - fi - - # QEMU appears to always relay absolute mouse coordinates relative to the screen that the mouse is - # pointed to, without any way for us to know what screen it was. So, when dealing with multiple - # displays force using relative coordinates only - SERENITY_KERNEL_CMDLINE="$SERENITY_KERNEL_CMDLINE vmmouse=off" - else - SERENITY_QEMU_DISPLAY_DEVICE="VGA,vgamem_mb=64 " - fi -fi - -if [ "$SERENITY_ARCH" = 'aarch64' ]; then - SERENITY_BOOT_DRIVE="-drive file=${SERENITY_DISK_IMAGE},if=sd,format=raw" -elif [ -z "${SERENITY_NVME_ENABLE}" ] || [ "${SERENITY_NVME_ENABLE}" -eq 1 ]; then - # NVME is enabled by default; disable by setting SERENITY_NVME_ENABLE=0 - SERENITY_BOOT_DRIVE="-drive file=${SERENITY_DISK_IMAGE},format=raw,index=0,media=disk,if=none,id=disk" - SERENITY_BOOT_DRIVE="${SERENITY_BOOT_DRIVE} -device i82801b11-bridge,id=bridge4 -device sdhci-pci,bus=bridge4" - SERENITY_BOOT_DRIVE="${SERENITY_BOOT_DRIVE} -device nvme,serial=deadbeef,drive=disk,bus=bridge4,logical_block_size=4096,physical_block_size=4096" - SERENITY_KERNEL_CMDLINE="${SERENITY_KERNEL_CMDLINE} root=nvme0:1:0" -else - SERENITY_BOOT_DRIVE="-drive file=${SERENITY_DISK_IMAGE},format=raw,index=0,media=disk,id=disk" -fi - -if [ -n "${SERENITY_USE_SDCARD}" ] && [ "${SERENITY_USE_SDCARD}" -eq 1 ]; then - SERENITY_BOOT_DRIVE="-device sdhci-pci -device sd-card,drive=sd-boot-drive -drive id=sd-boot-drive,if=none,format=raw,file=${SERENITY_DISK_IMAGE}" - SERENITY_KERNEL_CMDLINE="$SERENITY_KERNEL_CMDLINE root=sd2:0:0" -fi -if [ -n "${SERENITY_USE_USBDRIVE}" ] && [ "${SERENITY_USE_USBDRIVE}" -eq 1 ]; then - SERENITY_BOOT_DRIVE="-device usb-storage,drive=usbstick -drive if=none,id=usbstick,format=raw,file=${SERENITY_DISK_IMAGE}" - # FIXME: Find a better way to address the usb drive - SERENITY_KERNEL_CMDLINE="$SERENITY_KERNEL_CMDLINE root=block3:0" -fi - -if [ -z "$SERENITY_HOST_IP" ]; then - SERENITY_HOST_IP="127.0.0.1" -fi - -if command -v wslpath >/dev/null; then - SERENITY_DISABLE_GDB_SOCKET=1 -fi - -if [ "$(uname)" = "Darwin" ] && - [ "${SERENITY_VIRT_TECH_ARG}" = "--accel hvf" ]; then - # HVF doesn't support gdbstub per https://wiki.qemu.org/Features/HVF - SERENITY_DISABLE_GDB_SOCKET=1 -fi - -if [ -z "$SERENITY_DISABLE_GDB_SOCKET" ]; then - SERENITY_EXTRA_QEMU_ARGS="$SERENITY_EXTRA_QEMU_ARGS -gdb tcp:${SERENITY_HOST_IP}:1234" -fi - -if [ -z "$SERENITY_ETHERNET_DEVICE_TYPE" ]; then - SERENITY_ETHERNET_DEVICE_TYPE="e1000" -fi - -if [ "$SERENITY_ARCH" = "aarch64" ]; then - SERENITY_NETFLAGS= - SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE= -else - SERENITY_NETFLAGS="-netdev user,id=breh,hostfwd=tcp:${SERENITY_HOST_IP}:8888-10.0.2.15:8888,hostfwd=tcp:${SERENITY_HOST_IP}:8823-10.0.2.15:23,hostfwd=tcp:${SERENITY_HOST_IP}:8000-10.0.2.15:8000,hostfwd=tcp:${SERENITY_HOST_IP}:2222-10.0.2.15:22" - SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE=" - $SERENITY_NETFLAGS - -device $SERENITY_ETHERNET_DEVICE_TYPE,netdev=breh - " -fi - -# add -machine vmport=off below to run the machine with ps/2 mouse -if [ -z "$SERENITY_MACHINE" ]; then - if [ "$SERENITY_ARCH" = "aarch64" ]; then - SERENITY_MACHINE=" - -M raspi3b - -serial stdio - " - else - SERENITY_MACHINE=" - -m $SERENITY_RAM_SIZE - -smp $SERENITY_CPUS - -display $SERENITY_QEMU_DISPLAY_BACKEND - -device $SERENITY_QEMU_DISPLAY_DEVICE - -device virtio-serial,max_ports=2 - -device virtconsole,chardev=stdout - -device isa-debugcon,chardev=stdout - -device virtio-rng-pci - $SERENITY_AUDIO_BACKEND - $SERENITY_AUDIO_PC_SPEAKER - $SERENITY_AUDIO_DEVICE - -device pci-bridge,chassis_nr=1,id=bridge1 -device $SERENITY_ETHERNET_DEVICE_TYPE,bus=bridge1 - -device i82801b11-bridge,bus=bridge1,id=bridge2 -device sdhci-pci,bus=bridge2 - -device i82801b11-bridge,id=bridge3 -device sdhci-pci,bus=bridge3 - -device ich9-ahci,bus=bridge3 - -chardev stdio,id=stdout,mux=on - " - fi -fi - -if [ "$NATIVE_WINDOWS_QEMU" -ne "1" ]; then - SERENITY_MACHINE="$SERENITY_MACHINE - -qmp unix:qmp-sock,server,nowait" -fi - -if [ "$SERENITY_ARCH" = "aarch64" ]; then - SERENITY_KERNEL_AND_INITRD=" - -kernel Kernel/Kernel - " -else - SERENITY_KERNEL_AND_INITRD=" - -kernel Kernel/Prekernel/Prekernel - -initrd Kernel/Kernel - " -fi - - -[ -z "$SERENITY_COMMON_QEMU_ARGS" ] && SERENITY_COMMON_QEMU_ARGS=" -$SERENITY_EXTRA_QEMU_ARGS -$SERENITY_MACHINE -$SERENITY_BOOT_DRIVE --cpu $SERENITY_QEMU_CPU --name SerenityOS --d guest_errors --usb -$SERENITY_SPICE_SERVER_CHARDEV -" - -if [ "$SERENITY_ARCH" != "aarch64" ]; then - if [ "${SERENITY_SPICE}" ] && "${SERENITY_QEMU_BIN}" -chardev help | grep -iq spice; then - SERENITY_COMMON_QEMU_ARGS="$SERENITY_COMMON_QEMU_ARGS - -spice port=5930,agent-mouse=off,disable-ticketing=on - " - fi - if "${SERENITY_QEMU_BIN}" -chardev help | grep -iq 'spice\|vdagent'; then - SERENITY_COMMON_QEMU_ARGS="$SERENITY_COMMON_QEMU_ARGS - -device virtserialport,chardev=vdagent,nr=1 - " - fi -fi - -[ -z "$SERENITY_COMMON_QEMU_ISA_PC_ARGS" ] && SERENITY_COMMON_QEMU_ISA_PC_ARGS=" -$SERENITY_EXTRA_QEMU_ARGS --m $SERENITY_RAM_SIZE --cpu qemu64 --machine isapc --d guest_errors --device isa-vga --chardev stdio,id=stdout,mux=on --device isa-debugcon,chardev=stdout -$SERENITY_BOOT_DRIVE -" - -[ -z "$SERENITY_COMMON_QEMU_MICROVM_ARGS" ] && SERENITY_COMMON_QEMU_MICROVM_ARGS=" -$SERENITY_EXTRA_QEMU_ARGS --m $SERENITY_RAM_SIZE --machine microvm,pit=on,rtc=on,pic=on --cpu qemu64 --d guest_errors --chardev stdio,id=stdout,mux=on --device isa-debugcon,chardev=stdout --device isa-vga --device isa-ide -$SERENITY_BOOT_DRIVE --device i8042 --device ide-hd,drive=disk -" - -[ -z "$SERENITY_COMMON_QEMU_Q35_ARGS" ] && SERENITY_COMMON_QEMU_Q35_ARGS=" -$SERENITY_EXTRA_QEMU_ARGS --m $SERENITY_RAM_SIZE --cpu $SERENITY_QEMU_CPU --machine q35 --d guest_errors --smp $SERENITY_CPUS --vga none --device vmware-svga --device ich9-usb-ehci1,bus=pcie.0,multifunction=on,addr=0x5.0x0 --device ich9-usb-ehci2,bus=pcie.0,addr=0x5.0x2 --device ich9-usb-uhci1,bus=pcie.0,multifunction=on,addr=0x7.0x0 --device ich9-usb-uhci2,bus=pcie.0,addr=0x7.0x1 --device ich9-usb-uhci3,bus=pcie.0,addr=0x7.0x2 --device ich9-usb-uhci4,bus=pcie.0,addr=0x7.0x3 --device ich9-usb-uhci5,bus=pcie.0,addr=0x7.0x4 --device ich9-usb-uhci6,bus=pcie.0,addr=0x7.0x5 --device pcie-root-port,port=0x10,chassis=1,id=pcie.1,bus=pcie.0,multifunction=on,addr=0x6 --device pcie-root-port,port=0x11,chassis=2,id=pcie.2,bus=pcie.0,addr=0x6.0x1 --device pcie-root-port,port=0x12,chassis=3,id=pcie.3,bus=pcie.0,addr=0x6.0x2 --device pcie-root-port,port=0x13,chassis=4,id=pcie.4,bus=pcie.0,addr=0x6.0x3 --device pcie-root-port,port=0x14,chassis=5,id=pcie.5,bus=pcie.0,addr=0x6.0x4 --device pcie-root-port,port=0x15,chassis=6,id=pcie.6,bus=pcie.0,addr=0x6.0x5 --device pcie-root-port,port=0x16,chassis=7,id=pcie.7,bus=pcie.0,addr=0x6.0x6 --device pcie-root-port,port=0x17,chassis=8,id=pcie.8,bus=pcie.0,addr=0x6.0x7 --device ich9-intel-hda,bus=pcie.2,addr=0x03.0x0 --device bochs-display --device nec-usb-xhci,bus=pcie.2,addr=0x11.0x0 --device pci-bridge,chassis_nr=1,id=bridge1,bus=pcie.4,addr=0x3.0x0 --device sdhci-pci,bus=bridge1,addr=0x1.0x0 --display $SERENITY_QEMU_DISPLAY_BACKEND --device virtio-serial --chardev stdio,id=stdout,mux=on --device virtconsole,chardev=stdout --device isa-debugcon,chardev=stdout --device virtio-rng-pci -$SERENITY_AUDIO_BACKEND -$SERENITY_AUDIO_PC_SPEAKER -$SERENITY_BOOT_DRIVE -" - -export SDL_VIDEO_X11_DGAMOUSE=0 - -: "${SERENITY_BUILD:=.}" -cd -P -- "$SERENITY_BUILD" || die "Could not cd to \"$SERENITY_BUILD\"" - -if [ "$SERENITY_RUN" = "b" ]; then - # Meta/run.sh b: bochs - [ -z "$SERENITY_BOCHSRC" ] && { - # Make sure that SERENITY_SOURCE_DIR is set and not empty - [ -z "$SERENITY_SOURCE_DIR" ] && die 'SERENITY_SOURCE_DIR not set or empty' - SERENITY_BOCHSRC="$SERENITY_SOURCE_DIR/Meta/bochsrc" - } - "$SERENITY_BOCHS_BIN" -q -f "$SERENITY_BOCHSRC" -elif [ "$SERENITY_RUN" = "qn" ]; then - # Meta/run.sh qn: qemu without network - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ARGS \ - -device $SERENITY_ETHERNET_DEVICE_TYPE \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" -elif [ "$SERENITY_RUN" = "qtap" ]; then - # Meta/run.sh qtap: qemu with tap - sudo ip tuntap del dev tap0 mode tap || true - sudo ip tuntap add dev tap0 mode tap user "$(id -u)" - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_PACKET_LOGGING_ARG \ - -netdev tap,ifname=tap0,id=br0 \ - -device $SERENITY_ETHERNET_DEVICE_TYPE,netdev=br0 \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" - sudo ip tuntap del dev tap0 mode tap -elif [ "$SERENITY_RUN" = "qgrub" ] || [ "$SERENITY_RUN" = "qextlinux" ]; then - # Meta/run.sh qgrub: qemu with grub/extlinux - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_PACKET_LOGGING_ARG \ - $SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE -elif [ "$SERENITY_RUN" = "q35" ]; then - # Meta/run.sh q35: qemu (q35 chipset) with SerenityOS - echo "Starting SerenityOS with QEMU Q35 machine, Commandline: ${SERENITY_KERNEL_CMDLINE}" - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_Q35_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" -elif [ "$SERENITY_RUN" = "isapc" ]; then - # Meta/run.sh q35: qemu (q35 chipset) with SerenityOS - echo "Starting SerenityOS with QEMU ISA-PC machine, Commandline: ${SERENITY_KERNEL_CMDLINE}" - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ISA_PC_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_NETFLAGS \ - -device ne2k_isa,netdev=breh \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" -elif [ "$SERENITY_RUN" = "microvm" ]; then - # Meta/run.sh q35: qemu (q35 chipset) with SerenityOS - echo "Starting SerenityOS with QEMU MicroVM machine, Commandline: ${SERENITY_KERNEL_CMDLINE}" - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_MICROVM_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_NETFLAGS \ - -device ne2k_isa,netdev=breh \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" -elif [ "$SERENITY_RUN" = "q35grub" ]; then - # Meta/run.sh q35grub: qemu (q35 chipset) with SerenityOS, using a grub disk image - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_Q35_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE -elif [ "$SERENITY_RUN" = "limine" ]; then - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG -elif [ "$SERENITY_RUN" = "ci" ]; then - # Meta/run.sh ci: qemu in text mode - echo "Running QEMU in CI" - if [ "$SERENITY_ARCH" = "aarch64" ]; then - "$SERENITY_QEMU_BIN" \ - $SERENITY_EXTRA_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_BOOT_DRIVE \ - -M raspi3b \ - -d guest_errors \ - -no-reboot \ - -monitor none \ - -display none \ - -serial file:debug.log \ - -serial stdio \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" - else - "$SERENITY_QEMU_BIN" \ - $SERENITY_EXTRA_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_BOOT_DRIVE \ - -m $SERENITY_RAM_SIZE \ - -cpu $SERENITY_QEMU_CPU \ - -d guest_errors \ - -no-reboot \ - -smp ${SERENITY_CPUS} \ - -device ich9-ahci \ - -serial stdio \ - -display none \ - -debugcon file:debug.log \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" - fi -else - # Meta/run.sh: qemu with user networking - "$SERENITY_QEMU_BIN" \ - $SERENITY_COMMON_QEMU_ARGS \ - $SERENITY_VIRT_TECH_ARG \ - $SERENITY_PACKET_LOGGING_ARG \ - $SERENITY_NETFLAGS_WITH_DEFAULT_DEVICE \ - $SERENITY_KERNEL_AND_INITRD \ - -append "${SERENITY_KERNEL_CMDLINE}" -fi +"$SCRIPT_DIR/run.py"