556 lines
18 KiB
Python
556 lines
18 KiB
Python
##########################################################################
|
|
# Copyright (c) 2009-2016 ETH Zurich.
|
|
# All rights reserved.
|
|
#
|
|
# This file is distributed under the terms in the attached LICENSE file.
|
|
# If you do not find this file, copies can be found by writing to:
|
|
# ETH Zurich D-INFK, Universitaetstr 6, CH-8092 Zurich. Attn: Systems Group.
|
|
##########################################################################
|
|
|
|
import os, debug, signal, shutil, time
|
|
import barrelfish
|
|
|
|
class Machine(object):
|
|
|
|
@classmethod
|
|
def validateArgs(cls, kwargs):
|
|
try:
|
|
kwargs['bootarch']
|
|
except KeyError as e:
|
|
raise TypeError("Missing key %s" % e.args[0])
|
|
|
|
def __init__(self, options, operations,
|
|
bootarch=None,
|
|
machine_name=None,
|
|
boot_timeout=360,
|
|
platform=None,
|
|
buildarchs=None,
|
|
ncores=1,
|
|
cores_per_socket=None,
|
|
kernel_args=[],
|
|
serial_binary="serial_kernel",
|
|
pci_args=[],
|
|
acpi_args=[],
|
|
eth0=(0xff, 0xff, 0xff),
|
|
perfcount_type=None,
|
|
boot_driver = None,
|
|
tickrate = 0,
|
|
uboot = False,
|
|
**kwargs):
|
|
|
|
self._name = "(unknown)"
|
|
self.options = options
|
|
self._operations = operations
|
|
|
|
self._bootarch = bootarch
|
|
|
|
self._machine_name = machine_name
|
|
|
|
if buildarchs is None:
|
|
buildarchs = [bootarch]
|
|
self._build_archs = buildarchs
|
|
assert(bootarch in buildarchs)
|
|
|
|
self._ncores = ncores
|
|
|
|
if cores_per_socket is None:
|
|
cores_per_socket = ncores
|
|
self._cores_per_socket = cores_per_socket
|
|
|
|
self._kernel_args = kernel_args
|
|
|
|
self._boot_driver = boot_driver
|
|
|
|
self._serial_binary = serial_binary
|
|
|
|
self._boot_timeout = boot_timeout
|
|
|
|
self._platform = platform
|
|
|
|
self._pci_args = pci_args
|
|
|
|
self._acpi_args = acpi_args
|
|
|
|
self._eth0 = eth0
|
|
|
|
self._perfcount_type = perfcount_type
|
|
|
|
self._tick_rate = tickrate
|
|
self._uboot = uboot
|
|
|
|
if bool(kwargs):
|
|
debug.warning("Machine base class does not understand the " +
|
|
"following machine arguments: %s" % str(kwargs))
|
|
|
|
def get_machine_name(self):
|
|
return self._machine_name
|
|
|
|
def get_bootarch(self):
|
|
"""Return the architecture for booting and base system services."""
|
|
return self._bootarch
|
|
|
|
def get_buildarchs(self):
|
|
"""Return the architectures that must be enabled in hake for this machine."""
|
|
return self._build_archs
|
|
|
|
def get_buildall_target(self):
|
|
"""Return a valid make target to build default set of modules
|
|
(previously 'all')"""
|
|
raise NotImplementedError
|
|
|
|
def get_ncores(self):
|
|
"""Returns absolute number of cores."""
|
|
return self._ncores
|
|
|
|
def get_coreids(self):
|
|
"""Returns the list of core IDs."""
|
|
return range(0, self.get_ncores()) # default behaviour for x86
|
|
|
|
# XXX: REMOVE ME. used only by lwip_loopback bench
|
|
def get_cores_per_socket(self):
|
|
"""Returns number of cores per socket."""
|
|
return self._cores_per_socket
|
|
|
|
def get_tickrate(self):
|
|
"""Returns clock rate in MHz."""
|
|
raise NotImplementedError
|
|
|
|
def get_perfcount_type(self):
|
|
"""Returns a string ('amd0f', 'amd10', or 'intel'), or None if unknown"""
|
|
return self._perfcount_type
|
|
|
|
def get_kernel_args(self):
|
|
"""Returns list of machine-specific arguments to add to the kernel command-line"""
|
|
return self._kernel_args
|
|
|
|
def get_boot_driver(self):
|
|
"""Returns list of machine-specific arguments to add to the kernel command-line"""
|
|
return self._boot_driver
|
|
|
|
def get_pci_args(self):
|
|
"""Returns list of machine-specific arguments to add to the PCI command-line"""
|
|
return self._pci_args
|
|
|
|
def get_acpi_args(self):
|
|
"""Returns list of machine-specific arguments to add to the ACPI command-line"""
|
|
return self._acpi_args
|
|
|
|
def get_platform(self):
|
|
"""Returns machine-specific platform specifier"""
|
|
return self._platform
|
|
|
|
def get_eth0(self):
|
|
"""Returns machine-specific bus:dev:fun for connected network interface of machine"""
|
|
# 0xff for all three elements should preserve kaluga default behaviour
|
|
return self._eth0
|
|
|
|
def get_serial_binary(self):
|
|
"""Returns a machine-specific binary name for the serial driver
|
|
(fallback if not implemented is the kernel serial driver)"""
|
|
return self._serial_binary
|
|
|
|
def get_boot_timeout(self):
|
|
"""Returns a machine-specific timeout (in seconds), or None for the default"""
|
|
return self._boot_timeout
|
|
|
|
def get_test_timeout(self):
|
|
"""Returns a machine-specific timeout (in seconds), or None for the default"""
|
|
return None
|
|
|
|
def get_tftp_dir(self):
|
|
"""Return a unique TFTP boot directory for this machine."""
|
|
print("DEPRECATED get_tftp_dir")
|
|
return self._operations.get_tftp_dir()
|
|
|
|
def set_bootmodules(self, modules):
|
|
"""Set the machine to boot from the given module data."""
|
|
print("DEPRECATED set_bootmodules")
|
|
return self._operations.set_bootmodules(modules)
|
|
|
|
def lock(self):
|
|
"""Lock the machine to prevent concurrent access."""
|
|
print("DEPRECATED lock")
|
|
return self._operations.lock()
|
|
|
|
def unlock(self):
|
|
"""Unlock an already-locked machine."""
|
|
print("DEPRECATED unlock")
|
|
return self._operations.unlock()
|
|
|
|
|
|
def setup(self):
|
|
"""Prepare a locked machine to be booted."""
|
|
print("DEPRECATED setup")
|
|
return self._operations.setup()
|
|
|
|
def reboot(self):
|
|
"""Reboot (or boot) the machine."""
|
|
print("DEPRECATED reboot")
|
|
return self._operations.reboot()
|
|
|
|
def shutdown(self):
|
|
"""Shut down/switch off the machine."""
|
|
print("DEPRECATED shutdown")
|
|
return self._operations.shutdown()
|
|
|
|
def get_output(self):
|
|
"""Returns a file object to the output of a locked machine."""
|
|
print("DEPRECATED get_output")
|
|
return self._operations.get_output()
|
|
|
|
def force_write(self, consolectrl):
|
|
print("DEPRECATED force_write")
|
|
return self._operations.force_write(consolectrl)
|
|
|
|
def getName(self):
|
|
return self._name
|
|
|
|
def setName(self, name):
|
|
self._name = name
|
|
|
|
def default_bootmodules(self):
|
|
"""Returns the default boot module configuration for the given machine."""
|
|
# FIXME: clean up / separate platform-specific logic
|
|
|
|
machine = self
|
|
a = machine.get_bootarch()
|
|
|
|
# set the kernel: elver on x86_64
|
|
if a == "x86_64":
|
|
kernel = "elver"
|
|
elif a == "armv7" or a == "armv8":
|
|
kernel = "cpu_%s" % machine.get_platform()
|
|
else:
|
|
kernel = "cpu"
|
|
|
|
m = barrelfish.BootModules(machine, prefix=("%s/sbin/" % a), kernel=kernel)
|
|
m.add_kernel_args(machine.get_kernel_args())
|
|
# default for all barrelfish archs
|
|
# hack: cpu driver is not called "cpu" for ARMv7 builds
|
|
if a == "armv7" :
|
|
m.add_module("cpu_%s" % machine.get_platform(), machine.get_kernel_args())
|
|
elif a == "armv8" :
|
|
# remove kernel
|
|
m.set_kernel(None)
|
|
# add cpu driver
|
|
m.set_cpu_driver(kernel, machine.get_kernel_args())
|
|
# add boot driver
|
|
m.set_boot_driver(machine.get_boot_driver())
|
|
else :
|
|
m.add_module("cpu", machine.get_kernel_args())
|
|
|
|
m.add_module("init")
|
|
m.add_module("mem_serv")
|
|
m.add_module("monitor")
|
|
m.add_module("ramfsd", ["boot"])
|
|
m.add_module("skb", ["boot"])
|
|
m.add_module("proc_mgmt", ["boot"])
|
|
m.add_module("spawnd", ["boot"])
|
|
m.add_module("startd", ["boot"])
|
|
m.add_module("/eclipseclp_ramfs.cpio.gz", ["nospawn"])
|
|
m.add_module("/skb_ramfs.cpio.gz", ["nospawn"])
|
|
m.add_module("corectrl", ["auto"])
|
|
|
|
# armv8
|
|
if a == "armv8" :
|
|
if not machine._uboot: # no ACPI on U-Boot
|
|
m.add_module("acpi", ["boot"])
|
|
m.add_module("kaluga", ["boot"])
|
|
|
|
# SKB and PCI are x86-only for the moment
|
|
if a == "x86_64" or a == "x86_32":
|
|
# Add acpi with machine-specific extra-arguments
|
|
m.add_module("acpi", ["boot"] + machine.get_acpi_args())
|
|
m.add_module("routing_setup", ["boot"])
|
|
|
|
# Add pci with machine-specific extra-arguments
|
|
m.add_module("pci", ["auto"] + machine.get_pci_args())
|
|
|
|
# Add kaluga with machine-specific bus:dev:fun triplet for eth0
|
|
# interface
|
|
m.add_module("kaluga",
|
|
["boot", "eth0=%d:%d:%d" % machine.get_eth0()])
|
|
|
|
# coreboot should work on armv7 now
|
|
if a == "armv7":
|
|
m.add_module("kaluga", machine.get_kaluga_args())
|
|
m.add_module("driverdomain_pl390", ["auto"])
|
|
m.add_module("serial_kernel", ["auto"])
|
|
m.add_module("serial_pl011", ["auto"])
|
|
m.add_module("int_route", [])
|
|
|
|
return m
|
|
|
|
class MachineOperations(object):
|
|
|
|
def __init__(self, machine):
|
|
self._machine = machine
|
|
|
|
def get_tftp_dir(self):
|
|
"""Return a unique TFTP boot directory for this machine."""
|
|
raise NotImplementedError
|
|
|
|
def set_bootmodules(self, modules):
|
|
"""Set the machine to boot from the given module data."""
|
|
raise NotImplementedError
|
|
|
|
def lock(self):
|
|
"""Lock the machine to prevent concurrent access."""
|
|
raise NotImplementedError
|
|
|
|
def unlock(self):
|
|
"""Unlock an already-locked machine."""
|
|
raise NotImplementedError
|
|
|
|
def setup(self):
|
|
"""Prepare a locked machine to be booted."""
|
|
raise NotImplementedError
|
|
|
|
def reboot(self):
|
|
"""Reboot (or boot) the machine."""
|
|
raise NotImplementedError
|
|
|
|
def shutdown(self):
|
|
"""Shut down/switch off the machine."""
|
|
raise NotImplementedError
|
|
|
|
def get_output(self):
|
|
"""Returns a file object to the output of a locked machine."""
|
|
raise NotImplementedError
|
|
|
|
def force_write(self, consolectrl):
|
|
raise NotImplementedError
|
|
|
|
class MachineLockedError(Exception):
|
|
"""May be raised by lock() when the machine is locked by another user."""
|
|
pass
|
|
|
|
class ARMMachineBase(Machine):
|
|
|
|
@classmethod
|
|
def validateArgs(cls, kwargs):
|
|
super(ARMMachineBase, cls).validateArgs(kwargs)
|
|
try:
|
|
kwargs['platform']
|
|
except KeyError as e:
|
|
raise TypeError("Missing key %s" % e.args[0])
|
|
|
|
def __init__(self, options, operations, **kwargs):
|
|
super(ARMMachineBase, self).__init__(options, operations, **kwargs)
|
|
self.menulst = None
|
|
self.mmap = None
|
|
self.kernel_args = None
|
|
self.kaluga_args = None
|
|
self.menulst_template = "menu.lst." + self.get_bootarch() + "_" + \
|
|
self.get_platform() + ("_%d" % self.get_ncores())
|
|
self._set_kernel_image()
|
|
|
|
def _get_template_menu_lst(self):
|
|
"""Read menu lst in source tree"""
|
|
if self.menulst is None:
|
|
template_menulst = os.path.join(self.options.sourcedir, "hake",
|
|
self.menulst_template)
|
|
with open(template_menulst) as f:
|
|
self.menulst = f.readlines()
|
|
|
|
return self.menulst
|
|
|
|
def _set_kernel_image(self):
|
|
if self.options.existingbuild:
|
|
self.kernel_img = os.path.join(self.options.existingbuild, self.imagename)
|
|
else:
|
|
self.kernel_img = os.path.join(self.options.buildbase,
|
|
self.options.builds[0].name,
|
|
self.imagename)
|
|
|
|
def get_kernel_args(self):
|
|
if self.kernel_args is None:
|
|
for line in self._get_template_menu_lst():
|
|
if line.startswith("kernel") or line.startswith("cpudriver"):
|
|
pts = line.strip().split(" ", 2)
|
|
if len(pts) == 3:
|
|
self.kernel_args = pts[-1].split(" ")
|
|
else:
|
|
self.kernel_args = []
|
|
return self.kernel_args
|
|
|
|
def _get_mmap(self):
|
|
"""Grab MMAP data from menu lst in source tree"""
|
|
if self.mmap is None:
|
|
self.mmap = []
|
|
for line in self._get_template_menu_lst():
|
|
if line.startswith("mmap"):
|
|
self.mmap.append(line)
|
|
|
|
debug.debug("got MMAP:\n %s" % " ".join(self.mmap))
|
|
return self.mmap
|
|
|
|
def get_kaluga_args(self):
|
|
if self.kaluga_args is None:
|
|
for line in self._get_template_menu_lst():
|
|
if 'kaluga' in line:
|
|
_,_,args = line.strip().split(' ', 2)
|
|
self.kaluga_args = args.split(' ')
|
|
break
|
|
return self.kaluga_args
|
|
|
|
def _write_menu_lst(self, data, path):
|
|
debug.verbose('writing %s' % path)
|
|
debug.debug(data)
|
|
f = open(path, 'w')
|
|
f.write(data)
|
|
for line in self._get_mmap():
|
|
f.write(line)
|
|
f.close()
|
|
|
|
class ARMSimulatorBase(ARMMachineBase):
|
|
|
|
def __init__(self, options, operations,
|
|
boot_timeout=20, **kwargs):
|
|
super(ARMSimulatorBase, self).__init__(options, operations,
|
|
boot_timeout=boot_timeout,
|
|
**kwargs)
|
|
|
|
def get_tickrate(self):
|
|
return None
|
|
|
|
def get_test_timeout(self):
|
|
"""Default test timeout for ARM simulators: 10min"""
|
|
return 10 * 60
|
|
|
|
def get_machine_name(self):
|
|
return self.name
|
|
|
|
class ARMSimulatorOperations(MachineOperations):
|
|
|
|
def __init__(self, machine):
|
|
super(ARMSimulatorOperations, self).__init__(machine)
|
|
self.child = None
|
|
self.telnet = None
|
|
self.tftp_dir = None
|
|
self.simulator_start_timeout = 5 # seconds
|
|
|
|
def setup(self):
|
|
pass
|
|
|
|
def force_write(self, consolectrl):
|
|
pass
|
|
|
|
def lock(self):
|
|
pass
|
|
|
|
def unlock(self):
|
|
pass
|
|
|
|
def get_free_port(self):
|
|
import socket
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.bind(('', 0))
|
|
# extract port from addrinfo
|
|
self.telnet_port = s.getsockname()[1]
|
|
s.close()
|
|
|
|
def _get_cmdline(self):
|
|
raise NotImplementedError
|
|
|
|
def _kill_child(self):
|
|
# terminate child if running
|
|
if self.child:
|
|
try:
|
|
os.kill(self.child.pid, signal.SIGTERM)
|
|
except OSError, e:
|
|
debug.verbose("Caught OSError trying to kill child: %r" % e)
|
|
except Exception, e:
|
|
debug.verbose("Caught exception trying to kill child: %r" % e)
|
|
try:
|
|
self.child.wait()
|
|
except Exception, e:
|
|
debug.verbose(
|
|
"Caught exception while waiting for child: %r" % e)
|
|
self.child = None
|
|
|
|
def shutdown(self):
|
|
debug.verbose('Simulator:shutdown requested');
|
|
debug.verbose('terminating simulator')
|
|
if not self.child is None:
|
|
try:
|
|
self.child.terminate()
|
|
except OSError, e:
|
|
debug.verbose("Error when trying to terminate simulator: %r" % e)
|
|
debug.verbose('closing telnet connection')
|
|
if self.telnet is not None:
|
|
self.output.close()
|
|
self.telnet.close()
|
|
# try to cleanup tftp tree if needed
|
|
if self.tftp_dir and os.path.isdir(self.tftp_dir):
|
|
shutil.rmtree(self.tftp_dir, ignore_errors=True)
|
|
self.tftp_dir = None
|
|
|
|
def get_output(self):
|
|
# wait a bit to give the simulator time to listen for a telnet connection
|
|
if self.child.poll() != None: # Check if child is down
|
|
print 'Simulator is down, return code is %d' % self.child.returncode
|
|
return None
|
|
|
|
# use telnetlib
|
|
import telnetlib
|
|
self.telnet_connected = False
|
|
while not self.telnet_connected:
|
|
try:
|
|
# RH 08.08.2018 The gem5 test seems to have problems with using localhost
|
|
# instead of 127.0.0.1
|
|
self.telnet = telnetlib.Telnet("127.0.0.1", self.telnet_port)
|
|
self.telnet_connected = True
|
|
self.output = self.telnet.get_socket().makefile()
|
|
except IOError, e:
|
|
errno, msg = e
|
|
if errno != 111: # connection refused
|
|
debug.error("telnet: %s [%d]" % (msg, errno))
|
|
else:
|
|
self.telnet_connected = False
|
|
time.sleep(self.simulator_start_timeout)
|
|
|
|
return self.output
|
|
|
|
class MachineFactory:
|
|
|
|
machineFactories = {}
|
|
|
|
def __init__(self, name, machineClass, kwargs):
|
|
self._class = machineClass
|
|
self._kwargs = kwargs
|
|
self._name = name
|
|
|
|
@classmethod
|
|
def addMachine(cls, name, machineClass, **kwargs):
|
|
cls.machineFactories[name] = MachineFactory(name, machineClass, kwargs)
|
|
machineClass.validateArgs(kwargs)
|
|
|
|
def getName(self):
|
|
"""Get the name of the machine produced by this factory."""
|
|
return self._name
|
|
|
|
def createMachine(self, options):
|
|
"""Create a new machine instance."""
|
|
try:
|
|
machine = self._class(options, **self._kwargs)
|
|
except TypeError as e:
|
|
print("Machine class %s failed to instantiate: %s" % (str(self._class), str(e)))
|
|
raise TypeError(e)
|
|
machine.setName(self._name)
|
|
return machine
|
|
|
|
@classmethod
|
|
def createMachineByName(cls, name, options):
|
|
"""Create a new machine instance."""
|
|
return cls.machineFactories[name].createMachine(options)
|
|
|
|
# Assume that QEMU, FVP, pandaboard and Gem5 work everywhere if invoked
|
|
import qemu
|
|
import gem5
|
|
import fvp
|
|
import pandaboard
|
|
import imx8x
|
|
|
|
# Other site-specific modules will be loaded by the siteconfig module
|