summaryrefslogtreecommitdiffstats
path: root/scripts/qemucommand.py
blob: 30929acc097deb94ecb8ed5367dcdb077250fe47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
from os.path import exists, isdir, join, realpath, abspath
from os import listdir
import random
import socket
from shutil import copyfile
from subprocess import check_output

EXTENSIONS = {
    'intel-corei7-64': 'wic',
    'qemux86-64': 'ota-ext4'
}


def find_local_port(start_port):
    """"
    Find the next free TCP port after 'start_port'.
    """

    for port in range(start_port, start_port + 10):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.bind(('', port))
            return port
        except socket.error:
            print("Skipping port %d" % port)
        finally:
            s.close()
    raise Exception("Could not find a free TCP port")


def random_mac():
    """Return a random Ethernet MAC address
    @link https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml#ethernet-numbers-2
    """
    head = "ca:fe:"
    hex_digits = '0123456789abcdef'
    tail = ':'.join([random.choice(hex_digits) + random.choice(hex_digits) for _ in range(4)])
    return head + tail


class QemuCommand(object):
    def __init__(self, args):
        self.enable_u_boot = True
        self.dry_run = args.dry_run
        self.overlay = args.overlay
        self.host_fwd = None
        self.kernel = None
        self.drive_interface = "ide"

        if hasattr(args, 'uboot_enable'):
            self.enable_u_boot = args.uboot_enable.lower() in ("yes", "true", "1")

        # Rise an exception if U-Boot is disabled and overlay option is used
        if not self.enable_u_boot and self.overlay:
            raise EnvironmentError("An overlay option is currently supported only with U-Boot loader!")

        # If booting with u-boot is disabled we use "ext4" root fs instead of custom one "ota-ext4"
        if not self.enable_u_boot:
            self.drive_interface = "virtio"
            EXTENSIONS['qemux86-64'] = 'ext4'

        if args.machine:
            self.machine = args.machine
        else:
            if not isdir(args.dir):
                raise ValueError("Directory %s does not exist, please specify a --machine or a valid images directory" % args.dir)
            machines = listdir(args.dir)
            if len(machines) == 1:
                self.machine = machines[0]
            else:
                raise ValueError("Could not autodetect machine type. More than one entry in %s. Maybe --machine qemux86-64?" % args.dir)

        # If using an overlay with U-Boot, copy the rom when we create the
        # overlay so that we can keep it around just in case.
        if args.efi:
            self.bios = 'OVMF.fd'
        elif self.enable_u_boot:
            uboot_path = abspath(join(args.dir, self.machine, 'u-boot-qemux86-64.rom'))
            if self.overlay:
                new_uboot_path = self.overlay + '.u-boot.rom'
                if not exists(self.overlay):
                    if not exists(uboot_path):
                        raise ValueError("U-Boot image %s does not exist" % uboot_path)
                    if not exists(new_uboot_path):
                        if self.dry_run:
                            print("cp %s %s" % (uboot_path, new_uboot_path))
                        else:
                            copyfile(uboot_path, new_uboot_path)
                uboot_path = new_uboot_path
            if not exists(uboot_path) and not (self.dry_run and not exists(self.overlay)):
                raise ValueError("U-Boot image %s does not exist" % uboot_path)
            self.bios = uboot_path
        else:
            self.kernel = abspath(join(args.dir, self.machine, 'bzImage-qemux86-64.bin'))

        # If using an overlay, we need to keep the "backing" image around, as
        # bitbake will often clean it up, and the overlay silently depends on
        # the hardcoded path. The easiest solution is to keep the file and use
        # a relative path to it.
        if exists(args.imagename):
            image = realpath(args.imagename)
        else:
            ext = EXTENSIONS.get(self.machine, 'wic')
            image = join(args.dir, self.machine, '%s-%s.%s' % (args.imagename, self.machine, ext))
        if self.overlay:
            new_image_path = self.overlay + '.img'
            if not exists(self.overlay):
                if not exists(image):
                    raise ValueError("OS image %s does not exist" % image)
                if not exists(new_image_path):
                    if self.dry_run:
                        print("cp %s %s" % (image, new_image_path))
                    else:
                        copyfile(image, new_image_path)
            self.image = new_image_path
        else:
            self.image = realpath(image)
        if not exists(self.image) and not (self.dry_run and not exists(self.overlay)):
            raise ValueError("OS image %s does not exist" % self.image)

        if args.mac:
            self.mac_address = args.mac
        else:
            self.mac_address = random_mac()
        self.serial_port = find_local_port(8990)
        self.ssh_port = find_local_port(2222)
        if args.mem:
            self.mem = args.mem
        else:
            self.mem = "1G"
        if args.kvm is None:
            # Autodetect KVM using 'kvm-ok'
            try:
                check_output(['kvm-ok'])
                self.kvm = True
            except Exception:
                self.kvm = False
        else:
            self.kvm = args.kvm
        self.gui = not args.no_gui
        self.gdb = args.gdb
        self.pcap = args.pcap
        self.secondary_network = args.secondary_network

        # Append additional port forwarding to QEMU command line.
        if hasattr(args, 'host_forward'):
            self.host_fwd = args.host_forward

    def command_line(self):
        netuser = 'user,hostfwd=tcp:0.0.0.0:%d-:22,restrict=off' % self.ssh_port
        if self.gdb:
            netuser += ',hostfwd=tcp:0.0.0.0:2159-:2159'
        if self.host_fwd:
            netuser += ",hostfwd=" + self.host_fwd

        cmdline = [
            "qemu-system-x86_64",
        ]
        if self.enable_u_boot:
            cmdline += ["-bios", self.bios]
        else:
            cmdline += ["-kernel", self.kernel]

        if not self.overlay:
            cmdline += ["-drive", "file=%s,if=%s,format=raw,snapshot=on" % (self.image, self.drive_interface)]
        cmdline += [
            "-serial", "tcp:127.0.0.1:%d,server,nowait" % self.serial_port,
            "-m", self.mem,
            "-object", "rng-random,id=rng0,filename=/dev/urandom",
            "-device", "virtio-rng-pci,rng=rng0",
            "-net", netuser,
            "-net", "nic,macaddr=%s" % self.mac_address
        ]
        if self.pcap:
            cmdline += ['-net', 'dump,file=' + self.pcap]
        if self.secondary_network:
            cmdline += [
                '-netdev', 'socket,id=vlan1,mcast=230.0.0.1:1234,localaddr=127.0.0.1',
                '-device', 'e1000,netdev=vlan1,mac='+random_mac(),
            ]
        if self.gui:
            cmdline += [
                    "-usb",
                    "-device", "usb-tablet",
                    "-show-cursor",
                    "-vga", "std"
            ]
        else:
            cmdline += [
                    "-nographic",
                    "-monitor", "null",
            ]
        if self.kvm:
            cmdline += ['-enable-kvm', '-cpu', 'host']
        else:
            cmdline += ['-cpu', 'Haswell']
        if self.overlay:
            cmdline.append(self.overlay)

        # If booting with u-boot is disabled, add kernel command line arguments through qemu -append option
        if not self.enable_u_boot:
            cmdline += ["-append", "root=/dev/vda rw highres=off console=ttyS0 ip=dhcp"]
        return cmdline

    def img_command_line(self):
        cmdline = [
            "qemu-img", "create",
            "-o", "backing_file=%s" % self.image,
            "-f", "qcow2",
            self.overlay]
        return cmdline