environment/lib/vm/qemu.rb

218 lines
7 KiB
Ruby
Raw Normal View History

2025-08-11 17:36:55 +02:00
require "fileutils"
require "ostruct"
2025-08-17 10:30:42 +02:00
module DisplayMode
def self.none
0
end
def self.fullscreen
1
end
def self.window
2
end
end
2025-08-11 17:36:55 +02:00
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,
2025-08-26 08:52:28 +02:00
ram: 2048 * 8,
2025-08-15 09:56:42 +02:00
cpus: 1,
2025-08-17 10:30:42 +02:00
display: DisplayMode::none
2025-08-11 17:36:55 +02:00
}
2025-08-17 10:30:42 +02:00
# for testing only
defaults[:detach] = false
2025-08-26 08:51:37 +02:00
2025-08-17 17:21:38 +02:00
defaults[:display] = DisplayMode.fullscreen
2025-08-26 08:51:37 +02:00
defaults[:display] = DisplayMode.window
defaults[:display] = DisplayMode.none
2025-08-17 10:30:42 +02:00
2025-08-11 17:36:55 +02:00
opts = defaults.merge(options)
2025-08-17 10:30:42 +02:00
puts options
2025-08-11 17:36:55 +02:00
puts opts
qemu = qemu_bin_for(arch)
args = []
if System::OS == :macos && arch == :arm64
# args += ["-bios", "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"]
2025-08-26 08:52:28 +02:00
# cp /opt/homebrew/share/qemu/edk2-arm-vars.fd ~/edk2-arm-vars.fd
2025-08-11 17:36:55 +02:00
args += ["-drive", "if=pflash,format=raw,unit=0,readonly=on,file=/opt/homebrew/share/qemu/edk2-aarch64-code.fd"]
2025-08-26 08:52:28 +02:00
args += ["-drive", "if=pflash,format=raw,unit=1,file=/Users/agurgul/edk2-arm-vars.fd"]
2025-08-11 17:36:55 +02:00
2025-08-17 10:30:42 +02:00
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"
2025-08-15 09:56:42 +02:00
else
2025-08-17 10:30:42 +02:00
#args += ["-device", "virtio-gpu-device"]
if opts[:display] == DisplayMode::fullscreen
2025-08-26 08:51:37 +02:00
# #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"]
2025-08-17 17:21:38 +02:00
# args += ["-display", "sdl,gl=on,full-screen=on"]
2025-08-26 08:51:37 +02:00
args += ["-display", "cocoa,full-screen=on"]
2025-08-17 10:30:42 +02:00
else
args += ["-display", "cocoa"]
end
2025-08-15 09:56:42 +02:00
2025-08-17 10:30:42 +02:00
args += ["-device", "qemu-xhci,id=xhci"]
args += ["-device", "usb-kbd"]
args += ["-device", "usb-tablet"]
2025-08-11 17:36:55 +02:00
2025-08-17 10:30:42 +02:00
args += ["-device", "virtio-keyboard-device"]
args += ["-device", "virtio-mouse-device"]
args += ["-device", "virtio-gpu"]
2025-08-17 17:21:38 +02:00
#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
2025-08-17 10:30:42 +02:00
end
2025-08-11 17:36:55 +02:00
# 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
2025-08-26 08:52:28 +02:00
args += ["-drive", "file=#{disk_path},if=virtio,cache=writeback,format=raw,id=nvme0"]
#args += ["-device", "nvme,serial=nvme0,drive=nvme0,bootindex=2"]
2025-08-11 17:36:55 +02:00
if opts[:cdrom] != nil
2025-08-26 08:52:28 +02:00
#args += ["-cdrom", opts[:cdrom]]
# args += ["-device", "virtio-scsi-pci,id=scsi"]
# args += ["-drive", "if=none,id=cd,format=raw,file=#{opts[:cdrom]},media=cdrom"]
# args += ["-device", "scsi-cd,drive=cd,bootindex=1"]
args += ["-drive", "id=cd,format=raw,file=#{opts[:cdrom]},media=cdrom"]
args += ["-device", "usb-storage,drive=cd,bootindex=1"]
args += ["-device", "ramfb"]
# args += ["-device", "virtio-gpu-pci"]
# args += ["-display", "default,show-cursor=on"]
2025-08-11 17:36:55 +02:00
end
2025-08-26 08:52:28 +02:00
if opts[:tpm]
# brew install swtpm
# swtpm socket --tpm2 --ctrl type=unixio,path=./tpm/tpm.sock --tpmstate dir=./tpm --daemon
# args += ["-chardev", "socket,id=chrtpm,path=/Users/agurgul/Downloads/tpm/tpm.sock"]
# args += ["-tpmdev", "emulator,id=tpm0,chardev=chrtpm"]
# args += ["-device", "tpm-crb-device,tpmdev=tpm0"]
args += ["-chardev", "socket,id=chrtpm,path=/Users/agurgul/Downloads/tpm/tpm.sock"]
args += ["-tpmdev", "emulator,id=tpm0,chardev=chrtpm"]
#args += ["-device", "tpm-tis,tpmdev=tpm0"]
args += ["-device", "tpm-tis-device,tpmdev=tpm0"]
# nic user,ipv6=off,model=rtl8139,mac=84:1b:77:c9:03:a6
end
2025-08-17 10:30:42 +02:00
# args += ["-device", "virtio-net,netdev=n0", "-netdev", "user,id=n0"] # user-mode NAT
2025-08-11 17:36:55 +02:00
# 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]
2025-08-17 10:30:42 +02:00
log = File.open("log.txt", "w")
pid = Process.spawn(*cmd, pgroup: false, out: log, err: log)
2025-08-11 17:36:55 +02:00
Process.detach(pid)
puts "QEMU pid=#{pid}"
else
2025-08-17 10:30:42 +02:00
pid = Process.spawn(*cmd, pgroup: true, out: $stdout, err: $stderr)
2025-08-11 17:36:55 +02:00
Process.wait(pid)
status = $?
puts "Exit status: #{status.exitstatus}"
end
end
2025-08-17 10:30:42 +02:00
end
2025-08-26 08:52:28 +02:00