environment/lib/vm/qemu.rb
2025-08-26 08:51:37 +02:00

183 lines
5.6 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require "fileutils"
require "ostruct"
module DisplayMode
def self.none
0
end
def self.fullscreen
1
end
def self.window
2
end
end
module Qemu
def self.qemu_bin_for(arch)
{
x86_64: "qemu-system-x86_64",
x86_32: "qemu-system-i386",
arm64: "qemu-system-aarch64",
armv7: "qemu-system-arm",
armv6: "qemu-system-arm",
armv5: "qemu-system-arm",
riscv64: "qemu-system-riscv64",
ppc64le: "qemu-system-ppc64",
ppc64: "qemu-system-ppc64",
ppc32: "qemu-system-ppc",
s390x: "qemu-system-s390x",
mips64el: "qemu-system-mips64el",
mips64: "qemu-system-mips64",
mipsel: "qemu-system-mipsel",
mips: "qemu-system-mips",
loongarch64: "qemu-system-loongarch64",
sparc64: "qemu-system-sparc64",
alpha: "qemu-system-alpha",
hppa: "qemu-system-hppa",
ia64: "qemu-system-ia64",
}.fetch(arch) { raise "Unsupported arch: #{arch.inspect}" }
end
def self.accel_args
host = RbConfig::CONFIG["host_os"]
if host =~ /linux/i && File.exist?("/dev/kvm")
["-accel", "kvm"]
elsif host =~ /darwin/i
# hvf exists on Apple Silicon + Intel macOS; QEMU falls back if not available
["-accel", "hvf"]
elsif host =~ /freebsd/i
# if QEMU was built with it; otherwise it will ignore
["-accel", "bhyve"]
else
["-accel", "tcg"]
end
end
def self.machine_args_for(arch)
case arch
when :x86_64, :x86_32
[]
when :arm64
# -machine type=virt
# -cpu cortex-a72
["-machine", "virt", "-cpu", "max"]
when :armv7, :armv6, :armv5
["-machine", "virt", "-cpu", "cortex-a15"]
when :riscv64
["-machine", "virt"]
when :loongarch64
["-machine", "virt"]
else
[]
end
end
#def self.launch(arch, disk_path, cdrom = nil, detach = true)
def self.launch(arch, disk_path, **options)
defaults = {
arch: System::ARCH,
cdrom: nil,
detach: true,
ram: 2048,
cpus: 1,
display: DisplayMode::none
}
# for testing only
defaults[:detach] = false
defaults[:display] = DisplayMode.fullscreen
defaults[:display] = DisplayMode.window
defaults[:display] = DisplayMode.none
opts = defaults.merge(options)
puts options
puts opts
qemu = qemu_bin_for(arch)
args = []
if System::OS == :macos && arch == :arm64
# args += ["-bios", "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"]
args += ["-drive", "if=pflash,format=raw,unit=0,readonly=on,file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd"]
if opts[:display] == DisplayMode::none
port = 2222
args += ['-nographic']
args += ['-netdev', "user,id=net0,hostfwd=tcp:127.0.0.1:#{port}-:22"]
#args += ['-device', 'e1000,netdev=net0']
args += ['-device', 'virtio-net-pci,netdev=net0']
puts "ssh -p #{port} user@localhost"
else
#args += ["-device", "virtio-gpu-device"]
if opts[:display] == DisplayMode::fullscreen
# #args += ["-display", "cocoa,full-screen=on"]
# # attempts:
# #args += ["-display", "cocoa,full-screen=on,retina=on"]
# # brew install gtk+3 sdl2
# # args += ["-display", "sdl,gl=on,full-screen=on"]
# #args += ["-display", "gtk,gl=on,full-screen=on"]
# #args += ["-display", "cocoa,full-screen=on"]
# #args += ["-display", "cocoa,gl=es,full-screen=on"]
#### TODO: try make it work with custom build
# args += ["-device", "virtio-gpu-gl-pci"]
# args += ["-display", "sdl,gl=on,full-screen=on"]
args += ["-display", "cocoa,full-screen=on"]
else
args += ["-display", "cocoa"]
end
args += ["-device", "qemu-xhci,id=xhci"]
args += ["-device", "usb-kbd"]
args += ["-device", "usb-tablet"]
args += ["-device", "virtio-keyboard-device"]
args += ["-device", "virtio-mouse-device"]
args += ["-device", "virtio-gpu"]
#args += ['-nic', 'user,model=virtio-net-pci']
args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"]
# macOS vmnet (shares Macs LAN)
# -netdev vmnet-shared,id=n1 \
# -device virtio-net-pci,netdev=n1
end
# copy to /Users/artur/.cache/dat/vm/debian/arm64/debian/vars.fd
# /opt/homebrew/share/qemu/edk2-aarch64-vars.fd
# args += ["-drive", "if=pflash,format=raw,unit=1,file=/Users/artur/.cache/dat/vm/debian/arm64/debian/vars.fd"]
end
args += accel_args
args += machine_args_for(arch)
args += ["-m", opts[:ram].to_s, "-smp", opts[:cpus].to_s]
args += ["-name", "FirstVM", "-boot", "order=d"] # boot from CD first
args += ["-drive", "file=#{disk_path},if=virtio,cache=writeback,format=raw"]
if opts[:cdrom] != nil
args += ["-cdrom", opts[:cdrom]]
end
# args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"] # user-mode NAT
# optional: uncomment to run headless with VNC on :5901
# args += ["-display", "none", "-vnc", "127.0.0.1:1"]
cmd = [qemu, *args]
puts "Launching: #{cmd.join(' ')}"
if opts[:detach]
log = File.open("log.txt", "w")
pid = Process.spawn(*cmd, pgroup: false, out: log, err: log)
Process.detach(pid)
puts "QEMU pid=#{pid}"
else
pid = Process.spawn(*cmd, pgroup: true, out: $stdout, err: $stderr)
Process.wait(pid)
status = $?
puts "Exit status: #{status.exitstatus}"
end
end
end