github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/qemu/devices.go (about)

     1  // Copyright 2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package qemu
     6  
     7  import (
     8  	"fmt"
     9  	"net"
    10  	"sync/atomic"
    11  )
    12  
    13  // Device is a QEMU device to expose to a VM.
    14  type Device interface {
    15  	// Cmdline returns arguments to append to the QEMU command line for this device.
    16  	Cmdline() []string
    17  	// KArgs returns arguments that must be passed to the kernel for this device, or nil.
    18  	KArgs() []string
    19  }
    20  
    21  // Network is a Device that can connect multiple QEMU VMs to each other.
    22  //
    23  // Network uses the QEMU socket mechanism to connect multiple VMs with a simple
    24  // TCP socket.
    25  type Network struct {
    26  	port uint16
    27  
    28  	// numVMs must be atomically accessed so VMs can be started in parallel
    29  	// in goroutines.
    30  	numVMs uint32
    31  }
    32  
    33  // NewNetwork creates a new QEMU network between QEMU VMs.
    34  //
    35  // The network is closed from the world and only between the QEMU VMs.
    36  func NewNetwork() *Network {
    37  	return &Network{
    38  		port: 1234,
    39  	}
    40  }
    41  
    42  // NewVM returns a Device that can be used with a new QEMU VM.
    43  func (n *Network) NewVM() Device {
    44  	if n == nil {
    45  		return nil
    46  	}
    47  
    48  	newNum := atomic.AddUint32(&n.numVMs, 1)
    49  	num := newNum - 1
    50  
    51  	// MAC for the virtualized NIC.
    52  	//
    53  	// This is from the range of locally administered address ranges.
    54  	mac := net.HardwareAddr{0x0e, 0x00, 0x00, 0x00, 0x00, byte(num)}
    55  
    56  	args := []string{"-net", fmt.Sprintf("nic,macaddr=%s", mac)}
    57  	// Note: QEMU in CircleCI seems to in solve cases fail when using just ':1234' format.
    58  	// It fails with "address resolution failed for :1234: Name or service not known"
    59  	// hinting that this is somehow related to DNS resolution. To work around this,
    60  	// we explicitly bind to 127.0.0.1 (IPv6 [::1] is not parsed correctly by QEMU).
    61  	if num != 0 {
    62  		args = append(args, "-net", fmt.Sprintf("socket,connect=127.0.0.1:%d", n.port))
    63  	} else {
    64  		args = append(args, "-net", fmt.Sprintf("socket,listen=127.0.0.1:%d", n.port))
    65  	}
    66  	return networkImpl{args}
    67  }
    68  
    69  type networkImpl struct {
    70  	args []string
    71  }
    72  
    73  func (n networkImpl) Cmdline() []string {
    74  	return n.args
    75  }
    76  
    77  func (n networkImpl) KArgs() []string { return nil }
    78  
    79  // ReadOnlyDirectory is a Device that exposes a directory as a /dev/sda1
    80  // readonly vfat partition in the VM.
    81  type ReadOnlyDirectory struct {
    82  	// Dir is the directory to expose as a read-only vfat partition.
    83  	Dir string
    84  }
    85  
    86  func (rod ReadOnlyDirectory) Cmdline() []string {
    87  	if len(rod.Dir) == 0 {
    88  		return nil
    89  	}
    90  
    91  	// Expose the temp directory to QEMU as /dev/sda1
    92  	return []string{
    93  		"-drive", fmt.Sprintf("file=fat:rw:%s,if=none,id=tmpdir", rod.Dir),
    94  		"-device", "ich9-ahci,id=ahci",
    95  		"-device", "ide-drive,drive=tmpdir,bus=ahci.0",
    96  	}
    97  }
    98  
    99  func (ReadOnlyDirectory) KArgs() []string { return nil }
   100  
   101  // P9Directory is a Device that exposes a directory as a Plan9 (9p)
   102  // read-write filesystem in the VM.
   103  type P9Directory struct {
   104  	// Dir is the directory to expose as read-write 9p filesystem.
   105  	Dir string
   106  
   107  	// Boot: if true, indicates this is the root volume. For this to work,
   108  	// kernel args will need to be added - use KArgs() to get the args. There
   109  	// can only be one boot 9pfs at a time.
   110  	Boot bool
   111  
   112  	// Tag is an identifier that is used within the VM when mounting an fs,
   113  	// e.g. 'mount -t 9p my-vol-ident mountpoint'. If not specified, a default
   114  	// tag of 'tmpdir' will be used.
   115  	//
   116  	// Ignored if Boot is true, as the tag in that case is special.
   117  	//
   118  	// For non-boot devices, this is also used as the id linking the `-fsdev`
   119  	// and `-device` args together.
   120  	//
   121  	// Because the tag must be unique for each dir, if multiple non-boot
   122  	// P9Directory's are used, tag may be omitted for no more than one.
   123  	Tag string
   124  
   125  	// Arch is the architecture under test. This is used to determine the
   126  	// QEMU command line args.
   127  	Arch string
   128  }
   129  
   130  func (p P9Directory) Cmdline() []string {
   131  	if len(p.Dir) == 0 {
   132  		return nil
   133  	}
   134  
   135  	var tag, id string
   136  	if p.Boot {
   137  		tag = "/dev/root"
   138  	} else {
   139  		tag = p.Tag
   140  		if len(tag) == 0 {
   141  			tag = "tmpdir"
   142  		}
   143  	}
   144  	if p.Boot {
   145  		id = "rootdrv"
   146  	} else {
   147  		id = tag
   148  	}
   149  
   150  	// Expose the temp directory to QEMU
   151  	var deviceArgs string
   152  	switch p.Arch {
   153  	case "arm":
   154  		deviceArgs = fmt.Sprintf("virtio-9p-device,fsdev=%s,mount_tag=%s", id, tag)
   155  	default:
   156  		deviceArgs = fmt.Sprintf("virtio-9p-pci,fsdev=%s,mount_tag=%s", id, tag)
   157  	}
   158  
   159  	return []string{
   160  		// security_model=mapped-file seems to be the best choice. It gives
   161  		// us control over uid/gid/mode seen in the guest, without requiring
   162  		// elevated perms on the host.
   163  		"-fsdev", fmt.Sprintf("local,id=%s,path=%s,security_model=mapped-file", id, p.Dir),
   164  		"-device", deviceArgs,
   165  	}
   166  }
   167  
   168  func (p P9Directory) KArgs() []string {
   169  	if len(p.Dir) == 0 {
   170  		return nil
   171  	}
   172  	if p.Boot {
   173  		return []string{
   174  			"devtmpfs.mount=1",
   175  			"root=/dev/root",
   176  			"rootfstype=9p",
   177  			"rootflags=trans=virtio,version=9p2000.L",
   178  		}
   179  	}
   180  	return []string{
   181  		//seen as an env var by the init process
   182  		"UROOT_USE_9P=1",
   183  	}
   184  }
   185  
   186  // VirtioRandom is a Device that exposes a PCI random number generator to the
   187  // QEMU VM.
   188  type VirtioRandom struct{}
   189  
   190  func (VirtioRandom) Cmdline() []string {
   191  	return []string{"-device", "virtio-rng-pci"}
   192  }
   193  
   194  func (VirtioRandom) KArgs() []string { return nil }
   195  
   196  // ArbitraryArgs is a Device that allows users to add arbitrary arguments to
   197  // the QEMU command line.
   198  type ArbitraryArgs []string
   199  
   200  func (aa ArbitraryArgs) Cmdline() []string {
   201  	return aa
   202  }
   203  
   204  func (ArbitraryArgs) KArgs() []string { return nil }