1##########################################################################
2# Copyright (c) 2009-2016 ETH Zurich.
3# All rights reserved.
4#
5# This file is distributed under the terms in the attached LICENSE file.
6# If you do not find this file, copies can be found by writing to:
7# ETH Zurich D-INFK, Universitaetstr 6, CH-8092 Zurich. Attn: Systems Group.
8##########################################################################
9
10import os, debug, signal, shutil, time
11import barrelfish
12
13class Machine(object):
14
15    @classmethod
16    def validateArgs(cls, kwargs):
17        try:
18            kwargs['bootarch']
19        except KeyError as e:
20            raise TypeError("Missing key %s" % e.args[0])
21
22    def __init__(self, options, operations,
23                 bootarch=None,
24                 machine_name=None,
25                 boot_timeout=360,
26                 platform=None,
27                 buildarchs=None,
28                 ncores=1,
29                 cores_per_socket=None,
30                 kernel_args=[],
31                 serial_binary="serial_kernel",
32                 pci_args=[],
33                 acpi_args=[],
34                 eth0=(0xff, 0xff, 0xff),
35                 perfcount_type=None,
36                 boot_driver = None,
37                 tickrate = 0,
38                 uboot = False,
39                 **kwargs):
40
41        self._name = "(unknown)"
42        self.options = options
43        self._operations = operations
44
45        self._bootarch = bootarch
46
47        self._machine_name = machine_name
48
49        if buildarchs is None:
50            buildarchs = [bootarch]
51        self._build_archs = buildarchs
52        assert(bootarch in buildarchs)
53
54        self._ncores = ncores
55
56        if cores_per_socket is None:
57            cores_per_socket = ncores
58        self._cores_per_socket = cores_per_socket
59
60        self._kernel_args = kernel_args
61
62        self._boot_driver = boot_driver
63
64        self._serial_binary = serial_binary
65
66        self._boot_timeout = boot_timeout
67
68        self._platform = platform
69
70        self._pci_args = pci_args
71
72        self._acpi_args = acpi_args
73
74        self._eth0 = eth0
75
76        self._perfcount_type = perfcount_type
77
78        self._tick_rate = tickrate
79        self._uboot = uboot
80
81        if bool(kwargs):
82            debug.warning("Machine base class does not understand the " +
83                    "following machine arguments: %s" % str(kwargs))
84
85    def get_machine_name(self):
86        return self._machine_name
87
88    def get_bootarch(self):
89        """Return the architecture for booting and base system services."""
90        return self._bootarch
91
92    def get_buildarchs(self):
93        """Return the architectures that must be enabled in hake for this machine."""
94        return self._build_archs
95
96    def get_buildall_target(self):
97        """Return a valid make target to build default set of modules
98        (previously 'all')"""
99        raise NotImplementedError
100
101    def get_ncores(self):
102        """Returns absolute number of cores."""
103        return self._ncores
104
105    def get_coreids(self):
106        """Returns the list of core IDs."""
107        return range(0, self.get_ncores()) # default behaviour for x86
108
109    # XXX: REMOVE ME. used only by lwip_loopback bench
110    def get_cores_per_socket(self):
111        """Returns number of cores per socket."""
112        return self._cores_per_socket
113
114    def get_tickrate(self):
115        """Returns clock rate in MHz."""
116        raise NotImplementedError
117
118    def get_perfcount_type(self):
119        """Returns a string ('amd0f', 'amd10', or 'intel'), or None if unknown"""
120        return self._perfcount_type
121
122    def get_kernel_args(self):
123        """Returns list of machine-specific arguments to add to the kernel command-line"""
124        return self._kernel_args
125
126    def get_boot_driver(self):
127        """Returns list of machine-specific arguments to add to the kernel command-line"""
128        return self._boot_driver
129
130    def get_pci_args(self):
131        """Returns list of machine-specific arguments to add to the PCI command-line"""
132        return self._pci_args
133
134    def get_acpi_args(self):
135        """Returns list of machine-specific arguments to add to the ACPI command-line"""
136        return self._acpi_args
137
138    def get_platform(self):
139        """Returns machine-specific platform specifier"""
140        return self._platform
141
142    def get_eth0(self):
143        """Returns machine-specific bus:dev:fun for connected network interface of machine"""
144        # 0xff for all three elements should preserve kaluga default behaviour
145        return self._eth0
146
147    def get_serial_binary(self):
148        """Returns a machine-specific binary name for the serial driver
149        (fallback if not implemented is the kernel serial driver)"""
150        return self._serial_binary
151
152    def get_boot_timeout(self):
153        """Returns a machine-specific timeout (in seconds), or None for the default"""
154        return self._boot_timeout
155
156    def get_test_timeout(self):
157        """Returns a machine-specific timeout (in seconds), or None for the default"""
158        return None
159
160    def get_tftp_dir(self):
161        """Return a unique TFTP boot directory for this machine."""
162        print("DEPRECATED get_tftp_dir")
163        return self._operations.get_tftp_dir()
164
165    def set_bootmodules(self, modules):
166        """Set the machine to boot from the given module data."""
167        print("DEPRECATED set_bootmodules")
168        return self._operations.set_bootmodules(modules)
169
170    def lock(self):
171        """Lock the machine to prevent concurrent access."""
172        print("DEPRECATED lock")
173        return self._operations.lock()
174
175    def unlock(self):
176        """Unlock an already-locked machine."""
177        print("DEPRECATED unlock")
178        return self._operations.unlock()
179
180
181    def setup(self):
182        """Prepare a locked machine to be booted."""
183        print("DEPRECATED setup")
184        return self._operations.setup()
185
186    def reboot(self):
187        """Reboot (or boot) the machine."""
188        print("DEPRECATED reboot")
189        return self._operations.reboot()
190
191    def shutdown(self):
192        """Shut down/switch off the machine."""
193        print("DEPRECATED shutdown")
194        return self._operations.shutdown()
195
196    def get_output(self):
197        """Returns a file object to the output of a locked machine."""
198        print("DEPRECATED get_output")
199        return self._operations.get_output()
200
201    def force_write(self, consolectrl):
202        print("DEPRECATED force_write")
203        return self._operations.force_write(consolectrl)
204
205    def getName(self):
206        return self._name
207
208    def setName(self, name):
209        self._name = name
210
211    def default_bootmodules(self):
212        """Returns the default boot module configuration for the given machine."""
213        # FIXME: clean up / separate platform-specific logic
214
215        machine = self
216        a = machine.get_bootarch()
217
218        # set the kernel: elver on x86_64
219        if a == "x86_64":
220            kernel = "elver"
221        elif a == "armv7" or a == "armv8":
222            kernel = "cpu_%s" % machine.get_platform()
223        else:
224            kernel = "cpu"
225
226        m = barrelfish.BootModules(machine, prefix=("%s/sbin/" % a), kernel=kernel)
227        m.add_kernel_args(machine.get_kernel_args())
228        # default for all barrelfish archs
229        # hack: cpu driver is not called "cpu" for ARMv7 builds
230        if a == "armv7" :
231            m.add_module("cpu_%s" % machine.get_platform(), machine.get_kernel_args())
232        elif a == "armv8" :
233            # remove kernel
234            m.set_kernel(None)
235            # add cpu driver
236            m.set_cpu_driver(kernel, machine.get_kernel_args())
237            # add boot driver
238            m.set_boot_driver(machine.get_boot_driver())
239        else :
240            m.add_module("cpu", machine.get_kernel_args())
241
242        m.add_module("init")
243        m.add_module("mem_serv")
244        m.add_module("monitor")
245        m.add_module("ramfsd", ["boot"])
246        m.add_module("skb", ["boot"])
247        m.add_module("proc_mgmt", ["boot"])
248        m.add_module("spawnd", ["boot"])
249        m.add_module("startd", ["boot"])
250        m.add_module("/eclipseclp_ramfs.cpio.gz", ["nospawn"])
251        m.add_module("/skb_ramfs.cpio.gz", ["nospawn"])
252        m.add_module("corectrl", ["auto"])
253
254        # armv8
255        if a == "armv8" :
256            if not machine._uboot: # no ACPI on U-Boot
257                m.add_module("acpi", ["boot"])
258            m.add_module("kaluga", ["boot"])
259
260        # SKB and PCI are x86-only for the moment
261        if a == "x86_64" or a == "x86_32":
262            # Add acpi with machine-specific extra-arguments
263            m.add_module("acpi", ["boot"] + machine.get_acpi_args())
264            m.add_module("routing_setup", ["boot"])
265
266            # Add pci with machine-specific extra-arguments
267            m.add_module("pci", ["auto"] + machine.get_pci_args())
268
269            # Add kaluga with machine-specific bus:dev:fun triplet for eth0
270            # interface
271            m.add_module("kaluga",
272                    ["boot", "eth0=%d:%d:%d" % machine.get_eth0()])
273
274        # coreboot should work on armv7 now
275        if a == "armv7":
276            m.add_module("kaluga", machine.get_kaluga_args())
277            m.add_module("driverdomain_pl390", ["auto"])
278            m.add_module("serial_kernel", ["auto"])
279            m.add_module("serial_pl011",  ["auto"])
280            m.add_module("int_route", [])
281
282        return m
283
284class MachineOperations(object):
285
286    def __init__(self, machine):
287        self._machine = machine
288
289    def get_tftp_dir(self):
290        """Return a unique TFTP boot directory for this machine."""
291        raise NotImplementedError
292
293    def set_bootmodules(self, modules):
294        """Set the machine to boot from the given module data."""
295        raise NotImplementedError
296
297    def lock(self):
298        """Lock the machine to prevent concurrent access."""
299        raise NotImplementedError
300
301    def unlock(self):
302        """Unlock an already-locked machine."""
303        raise NotImplementedError
304
305    def setup(self):
306        """Prepare a locked machine to be booted."""
307        raise NotImplementedError
308
309    def reboot(self):
310        """Reboot (or boot) the machine."""
311        raise NotImplementedError
312
313    def shutdown(self):
314        """Shut down/switch off the machine."""
315        raise NotImplementedError
316
317    def get_output(self):
318        """Returns a file object to the output of a locked machine."""
319        raise NotImplementedError
320
321    def force_write(self, consolectrl):
322        raise NotImplementedError
323
324class MachineLockedError(Exception):
325    """May be raised by lock() when the machine is locked by another user."""
326    pass
327
328class ARMMachineBase(Machine):
329
330    @classmethod
331    def validateArgs(cls, kwargs):
332        super(ARMMachineBase, cls).validateArgs(kwargs)
333        try:
334            kwargs['platform']
335        except KeyError as e:
336            raise TypeError("Missing key %s" % e.args[0])
337
338    def __init__(self, options, operations, **kwargs):
339        super(ARMMachineBase, self).__init__(options, operations, **kwargs)
340        self.menulst = None
341        self.mmap = None
342        self.kernel_args = None
343        self.kaluga_args = None
344        self.menulst_template = "menu.lst." + self.get_bootarch() + "_" + \
345                                self.get_platform() + ("_%d" % self.get_ncores())
346        self._set_kernel_image()
347
348    def _get_template_menu_lst(self):
349        """Read menu lst in source tree"""
350        if self.menulst is None:
351            template_menulst = os.path.join(self.options.sourcedir, "hake",
352                    self.menulst_template)
353            with open(template_menulst) as f:
354                self.menulst = f.readlines()
355
356        return self.menulst
357
358    def _set_kernel_image(self):
359        if self.options.existingbuild:
360            self.kernel_img = os.path.join(self.options.existingbuild, self.imagename)
361        else:
362            self.kernel_img = os.path.join(self.options.buildbase,
363                                self.options.builds[0].name,
364                                self.imagename)
365
366    def get_kernel_args(self):
367        if self.kernel_args is None:
368            for line in self._get_template_menu_lst():
369                if line.startswith("kernel") or line.startswith("cpudriver"):
370                    pts = line.strip().split(" ", 2)
371                    if len(pts) == 3:
372                        self.kernel_args = pts[-1].split(" ")
373                    else:
374                        self.kernel_args = []
375        return self.kernel_args
376
377    def _get_mmap(self):
378        """Grab MMAP data from menu lst in source tree"""
379        if self.mmap is None:
380            self.mmap = []
381            for line in self._get_template_menu_lst():
382                if line.startswith("mmap"):
383                    self.mmap.append(line)
384
385        debug.debug("got MMAP:\n  %s" % "  ".join(self.mmap))
386        return self.mmap
387
388    def get_kaluga_args(self):
389        if self.kaluga_args is None:
390            for line in self._get_template_menu_lst():
391                if 'kaluga' in line:
392                    _,_,args = line.strip().split(' ', 2)
393                    self.kaluga_args = args.split(' ')
394                    break
395        return self.kaluga_args
396
397    def _write_menu_lst(self, data, path):
398        debug.verbose('writing %s' % path)
399        debug.debug(data)
400        f = open(path, 'w')
401        f.write(data)
402        for line in self._get_mmap():
403            f.write(line)
404        f.close()
405
406class ARMSimulatorBase(ARMMachineBase):
407
408    def __init__(self, options, operations,
409                 boot_timeout=20, **kwargs):
410        super(ARMSimulatorBase, self).__init__(options, operations,
411                boot_timeout=boot_timeout,
412                **kwargs)
413
414    def get_tickrate(self):
415        return None
416
417    def get_test_timeout(self):
418        """Default test timeout for ARM simulators: 10min"""
419        return 10 * 60
420
421    def get_machine_name(self):
422        return self.name
423
424class ARMSimulatorOperations(MachineOperations):
425
426    def __init__(self, machine):
427        super(ARMSimulatorOperations, self).__init__(machine)
428        self.child = None
429        self.telnet = None
430        self.tftp_dir = None
431        self.simulator_start_timeout = 5 # seconds
432
433    def setup(self):
434        pass
435
436    def force_write(self, consolectrl):
437        pass
438
439    def lock(self):
440        pass
441
442    def unlock(self):
443        pass
444
445    def get_free_port(self):
446        import socket
447        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
448        s.bind(('', 0))
449        # extract port from addrinfo
450        self.telnet_port = s.getsockname()[1]
451        s.close()
452
453    def _get_cmdline(self):
454        raise NotImplementedError
455
456    def _kill_child(self):
457        # terminate child if running
458        if self.child:
459            try:
460                os.kill(self.child.pid, signal.SIGTERM)
461            except OSError, e:
462                debug.verbose("Caught OSError trying to kill child: %r" % e)
463            except Exception, e:
464                debug.verbose("Caught exception trying to kill child: %r" % e)
465            try:
466                self.child.wait()
467            except Exception, e:
468                debug.verbose(
469                    "Caught exception while waiting for child: %r" % e)
470            self.child = None
471
472    def shutdown(self):
473        debug.verbose('Simulator:shutdown requested');
474        debug.verbose('terminating simulator')
475        if not self.child is None:
476            try:
477                self.child.terminate()
478            except OSError, e:
479                debug.verbose("Error when trying to terminate simulator: %r" % e)
480        debug.verbose('closing telnet connection')
481        if self.telnet is not None:
482            self.output.close()
483            self.telnet.close()
484        # try to cleanup tftp tree if needed
485        if self.tftp_dir and os.path.isdir(self.tftp_dir):
486            shutil.rmtree(self.tftp_dir, ignore_errors=True)
487        self.tftp_dir = None
488
489    def get_output(self):
490        # wait a bit to give the simulator time to listen for a telnet connection
491        if self.child.poll() != None: # Check if child is down
492            print 'Simulator is down, return code is %d' % self.child.returncode
493            return None
494
495        # use telnetlib
496        import telnetlib
497        self.telnet_connected = False
498        while not self.telnet_connected:
499            try:
500                # RH 08.08.2018 The gem5 test seems to have problems with using localhost
501                # instead of 127.0.0.1
502                self.telnet = telnetlib.Telnet("127.0.0.1", self.telnet_port)
503                self.telnet_connected = True
504                self.output = self.telnet.get_socket().makefile()
505            except IOError, e:
506                errno, msg = e
507                if errno != 111: # connection refused
508                    debug.error("telnet: %s [%d]" % (msg, errno))
509                else:
510                    self.telnet_connected = False
511            time.sleep(self.simulator_start_timeout)
512
513        return self.output
514
515class MachineFactory:
516
517    machineFactories = {}
518
519    def __init__(self, name, machineClass, kwargs):
520        self._class = machineClass
521        self._kwargs = kwargs
522        self._name = name
523
524    @classmethod
525    def addMachine(cls, name, machineClass, **kwargs):
526        cls.machineFactories[name] = MachineFactory(name, machineClass, kwargs)
527        machineClass.validateArgs(kwargs)
528
529    def getName(self):
530        """Get the name of the machine produced by this factory."""
531        return self._name
532
533    def createMachine(self, options):
534        """Create a new machine instance."""
535        try:
536            machine = self._class(options, **self._kwargs)
537        except TypeError as e:
538            print("Machine class %s failed to instantiate: %s" % (str(self._class), str(e)))
539            raise TypeError(e)
540        machine.setName(self._name)
541        return machine
542
543    @classmethod
544    def createMachineByName(cls, name, options):
545        """Create a new machine instance."""
546        return cls.machineFactories[name].createMachine(options)
547
548# Assume that QEMU, FVP, pandaboard and Gem5 work everywhere if invoked
549import qemu
550import gem5
551import fvp
552import pandaboard
553import imx8x
554
555# Other site-specific modules will be loaded by the siteconfig module
556