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