blob: 0c8d6aa611044889f419ca3a423c1abc689da1a2 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package qemu
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
DefaultNetwork = "10.0.2.0/24"
DefaultDHCPStart = "10.0.2.15"
DefaultGateway = "10.0.2.2"
DefaultDNS = "10.0.2.3"
)
const (
TargetAArch64 = "aarch64"
TargetX86_64 = "x86_64"
)
type Drive struct {
// ID is the block device identifier.
ID string
// File is the disk image file.
File string
// Addr is the PCI address of the block device.
Addr string
}
type Forward struct {
// HostPort is the port on the host.
HostPort int
// GuestPort is the port on the guest.
GuestPort int
}
type Netdev struct {
// ID is the network device identifier.
ID string
// User is a netdev user backend.
User *NetdevUser
// Tap is a netdev tap backend.
Tap *NetdevTap
// MAC is the network device MAC address.
MAC string
}
// NetdevUser defines a netdev backend giving user networking.
type NetdevUser struct {
// Network is the network block.
Network string
// DHCPStart is the address at which the DHCP allocation starts.
DHCPStart string
// DNS is the address of the builtin DNS server.
DNS string
// Host is the host IP address.
Host string
// Forwards are the host forwardings.
Forwards []Forward
}
// NetdevTap defines a netdev backend giving a tap interface.
type NetdevTap struct {
// Name is the name of the interface.
Name string
}
// Config gives a high-level configuration for QEMU on Fuchsia.
type Config struct {
// QEMUBin is a path to the QEMU binary.
Binary string
// Target is the QEMU target (e.g., "x86_64" or "aarch64").
Target string
// CPU is the number of CPUs.
CPU int
// Memory is the amount of RAM.
Memory int
// KVM gives whether to enable KVM.
KVM bool
// Kernel is the path to the kernel image.
Kernel string
// Initrd is the path to the initrd image.
Initrd string
// Drives are drives to mount inside the QEMU instance.
Drives []Drive
// Networks are networks to set up inside the QEMU instance.
Networks []Netdev
}
// CreateInvocation creates a QEMU invocation given a particular configuration, a list of
// images, and any specified command-line arguments.
func CreateInvocation(cfg Config, cmdlineArgs []string) ([]string, error) {
if _, err := os.Stat(cfg.Binary); err != nil {
return nil, fmt.Errorf("QEMU binary not found: %v", err)
}
absBinaryPath, err := filepath.Abs(cfg.Binary)
if err != nil {
return nil, err
}
invocation := []string{absBinaryPath}
switch cfg.Target {
case TargetAArch64:
if cfg.KVM {
invocation = append(invocation, "-machine", "virt,gic_version=host")
invocation = append(invocation, "-cpu", "host")
invocation = append(invocation, "-enable-kvm")
} else {
invocation = append(invocation, "-machine", "virt,gic_version=3")
invocation = append(invocation, "-machine", "virtualization=true")
invocation = append(invocation, "-cpu", "cortex-a53")
}
case TargetX86_64:
invocation = append(invocation, "-machine", "q35")
// TODO: this is Fuchsia specific, factor it out as another device struct.
// Necessary for userboot.shutdown to trigger properly, since it writes
// to 0xf4 to debug-exit in QEMU.
invocation = append(invocation, "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04")
if cfg.KVM {
invocation = append(invocation, "-cpu", "host")
invocation = append(invocation, "-enable-kvm")
} else {
invocation = append(invocation, "-cpu", "Haswell,+smap,-check,-fsgsbase")
}
default:
return nil, fmt.Errorf("cpu %q not recognized", cfg.Target)
}
invocation = append(invocation, "-m", fmt.Sprintf("%d", cfg.Memory))
invocation = append(invocation, "-smp", fmt.Sprintf("%d", cfg.CPU))
invocation = append(invocation, "-nographic")
invocation = append(invocation, "-serial", "stdio")
invocation = append(invocation, "-monitor", "none")
invocation = append(invocation, "-kernel", cfg.Kernel)
invocation = append(invocation, "-initrd", cfg.Initrd)
invocation = append(invocation, "-object,iothread,id=iothread0")
// TODO: maybe we should introduce Device interface with three different
// implementations: Drive, Netdev and ISADebugExit to cleanup the code
// below a bit.
for _, d := range cfg.Drives {
var drive strings.Builder
fmt.Fprintf(&drive, "id=%s,file=%s,format=raw,if=none,cache=unsafe,aio=threads", d.ID, d.File)
invocation = append(invocation, "-drive", drive.String())
var device strings.Builder
fmt.Fprintf(&device, "virtio-blk-pci,drive=%s,iothread=iothread0", d.ID)
if d.Addr != "" {
fmt.Fprintf(&device, ",addr=%s", d.Addr)
}
invocation = append(invocation, "-device", device.String())
}
for _, n := range cfg.Networks {
if n.ID == "" {
return nil, fmt.Errorf("a network must have an ID")
}
var netdev strings.Builder
if n.Tap != nil {
if n.Tap.Name == "" {
return nil, fmt.Errorf("network %q must specify a TAP interface name", n.ID)
}
// Overwrite any default configuration scripts with none; there is not currently a
// good use case for these parameters.
fmt.Fprintf(&netdev, "tap,id=%s,ifname=%s,script=no,downscript=no", n.ID, n.Tap.Name)
} else if n.User != nil {
fmt.Fprintf(&netdev, "user,id=%s", n.ID)
if n.User.Network != "" {
fmt.Fprintf(&netdev, ",net=%s", n.User.Network)
}
if n.User.DHCPStart != "" {
fmt.Fprintf(&netdev, ",dhcpstart=%s", n.User.DHCPStart)
}
if n.User.DNS != "" {
fmt.Fprintf(&netdev, ",dns=%s", n.User.DNS)
}
if n.User.Host != "" {
fmt.Fprintf(&netdev, ",host=%s", n.User.Host)
}
for _, f := range n.User.Forwards {
fmt.Fprintf(&netdev, ",hostfwd=tcp::%d-:%d", f.HostPort, f.GuestPort)
}
} else {
return nil, fmt.Errorf("network %q must specify a netdev backend", n.ID)
}
invocation = append(invocation, "-netdev", netdev.String())
var device strings.Builder
fmt.Fprintf(&device, "virtio-net-pci,netdev=%s", n.ID)
if n.MAC != "" {
fmt.Fprintf(&device, ",mac=%s", n.MAC)
}
invocation = append(invocation, "-device", device.String())
}
// Treat the absense of specified networks as a directive to disable networking entirely.
if len(cfg.Networks) == 0 {
invocation = append(invocation, "-net", "none")
}
invocation = append(invocation, "-append", strings.Join(cmdlineArgs, " "))
return invocation, nil
}