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