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 }