1# Copyright 2016 ARM Limited 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import re 17import subprocess 18import sys 19import shutil 20import time 21import types 22 23from devlib.exception import TargetError 24from devlib.host import PACKAGE_BIN_DIRECTORY 25from devlib.platform import Platform 26from devlib.utils.ssh import AndroidGem5Connection, LinuxGem5Connection 27 28class Gem5SimulationPlatform(Platform): 29 30 def __init__(self, name, 31 host_output_dir, 32 gem5_bin, 33 gem5_args, 34 gem5_virtio, 35 core_names=None, 36 core_clusters=None, 37 big_core=None, 38 model=None, 39 modules=None, 40 gem5_telnet_port=None): 41 42 # First call the parent class 43 super(Gem5SimulationPlatform, self).__init__(name, core_names, core_clusters, 44 big_core, model, modules) 45 46 # Start setting up the gem5 parameters/directories 47 # The gem5 subprocess 48 self.gem5 = None 49 self.gem5_port = gem5_telnet_port or None 50 self.stats_directory = host_output_dir 51 self.gem5_out_dir = os.path.join(self.stats_directory, "gem5") 52 self.gem5_interact_dir = '/tmp' # Host directory 53 self.executable_dir = None # Device directory 54 self.working_dir = None # Device directory 55 self.stdout_file = None 56 self.stderr_file = None 57 self.stderr_filename = None 58 if self.gem5_port is None: 59 # Allows devlib to pick up already running simulations 60 self.start_gem5_simulation = True 61 else: 62 self.start_gem5_simulation = False 63 64 # Find the first one that does not exist. Ensures that we do not re-use 65 # the directory used by someone else. 66 for i in xrange(sys.maxint): 67 directory = os.path.join(self.gem5_interact_dir, "wa_{}".format(i)) 68 try: 69 os.stat(directory) 70 continue 71 except OSError: 72 break 73 self.gem5_interact_dir = directory 74 self.logger.debug("Using {} as the temporary directory." 75 .format(self.gem5_interact_dir)) 76 77 # Parameters passed onto gem5 78 self.gem5args_binary = gem5_bin 79 self.gem5args_args = gem5_args 80 self.gem5args_virtio = gem5_virtio 81 self._check_gem5_command() 82 83 # Start the interaction with gem5 84 self._start_interaction_gem5() 85 86 def _check_gem5_command(self): 87 """ 88 Check if the command to start gem5 makes sense 89 """ 90 if self.gem5args_binary is None: 91 raise TargetError('Please specify a gem5 binary.') 92 if self.gem5args_args is None: 93 raise TargetError('Please specify the arguments passed on to gem5.') 94 self.gem5args_virtio = str(self.gem5args_virtio).format(self.gem5_interact_dir) 95 if self.gem5args_virtio is None: 96 raise TargetError('Please specify arguments needed for virtIO.') 97 98 def _start_interaction_gem5(self): 99 """ 100 Starts the interaction of devlib with gem5. 101 """ 102 103 # First create the input and output directories for gem5 104 if self.start_gem5_simulation: 105 # Create the directory to send data to/from gem5 system 106 self.logger.info("Creating temporary directory for interaction " 107 " with gem5 via virtIO: {}" 108 .format(self.gem5_interact_dir)) 109 os.mkdir(self.gem5_interact_dir) 110 111 # Create the directory for gem5 output (stats files etc) 112 if not os.path.exists(self.stats_directory): 113 os.mkdir(self.stats_directory) 114 if os.path.exists(self.gem5_out_dir): 115 raise TargetError("The gem5 stats directory {} already " 116 "exists.".format(self.gem5_out_dir)) 117 else: 118 os.mkdir(self.gem5_out_dir) 119 120 # We need to redirect the standard output and standard error for the 121 # gem5 process to a file so that we can debug when things go wrong. 122 f = os.path.join(self.gem5_out_dir, 'stdout') 123 self.stdout_file = open(f, 'w') 124 f = os.path.join(self.gem5_out_dir, 'stderr') 125 self.stderr_file = open(f, 'w') 126 # We need to keep this so we can check which port to use for the 127 # telnet connection. 128 self.stderr_filename = f 129 130 # Start gem5 simulation 131 self.logger.info("Starting the gem5 simulator") 132 133 command_line = "{} --outdir={} {} {}".format(self.gem5args_binary, 134 self.gem5_out_dir, 135 self.gem5args_args, 136 self.gem5args_virtio) 137 self.logger.debug("gem5 command line: {}".format(command_line)) 138 self.gem5 = subprocess.Popen(command_line.split(), 139 stdout=self.stdout_file, 140 stderr=self.stderr_file) 141 142 else: 143 # The simulation should already be running 144 # Need to dig up the (1) gem5 simulation in question (2) its input 145 # and output directories (3) virtio setting 146 self._intercept_existing_gem5() 147 148 # As the gem5 simulation is running now or was already running 149 # we now need to find out which telnet port it uses 150 self._intercept_telnet_port() 151 152 def _intercept_existing_gem5(self): 153 """ 154 Intercept the information about a running gem5 simulation 155 e.g. pid, input directory etc 156 """ 157 self.logger("This functionality is not yet implemented") 158 raise TargetError() 159 160 def _intercept_telnet_port(self): 161 """ 162 Intercept the telnet port of a running gem5 simulation 163 """ 164 165 if self.gem5 is None: 166 raise TargetError('The platform has no gem5 simulation! ' 167 'Something went wrong') 168 while self.gem5_port is None: 169 # Check that gem5 is running! 170 if self.gem5.poll(): 171 raise TargetError("The gem5 process has crashed with error code {}!".format(self.gem5.poll())) 172 173 # Open the stderr file 174 with open(self.stderr_filename, 'r') as f: 175 for line in f: 176 m = re.search(r"Listening for system connection on port (?P<port>\d+)", line) 177 if m: 178 port = int(m.group('port')) 179 if port >= 3456 and port < 5900: 180 self.gem5_port = port 181 break 182 # Check if the sockets are not disabled 183 m = re.search(r"Sockets disabled, not accepting terminal connections", line) 184 if m: 185 raise TargetError("The sockets have been disabled!" 186 "Pass --listener-mode=on to gem5") 187 else: 188 time.sleep(1) 189 190 def init_target_connection(self, target): 191 """ 192 Update the type of connection in the target from here 193 """ 194 if target.os == 'linux': 195 target.conn_cls = LinuxGem5Connection 196 else: 197 target.conn_cls = AndroidGem5Connection 198 199 def setup(self, target): 200 """ 201 Deploy m5 if not yet installed 202 """ 203 m5_path = target.get_installed('m5') 204 if m5_path is None: 205 m5_path = self._deploy_m5(target) 206 target.conn.m5_path = m5_path 207 208 # Set the terminal settings for the connection to gem5 209 self._resize_shell(target) 210 211 def update_from_target(self, target): 212 """ 213 Set the m5 path and if not yet installed, deploy m5 214 Overwrite certain methods in the target that either can be done 215 more efficiently by gem5 or don't exist in gem5 216 """ 217 m5_path = target.get_installed('m5') 218 if m5_path is None: 219 m5_path = self._deploy_m5(target) 220 target.conn.m5_path = m5_path 221 222 # Overwrite the following methods (monkey-patching) 223 self.logger.debug("Overwriting the 'capture_screen' method in target") 224 # Housekeeping to prevent recursion 225 setattr(target, 'target_impl_capture_screen', target.capture_screen) 226 target.capture_screen = types.MethodType(_overwritten_capture_screen, target) 227 self.logger.debug("Overwriting the 'reset' method in target") 228 target.reset = types.MethodType(_overwritten_reset, target) 229 self.logger.debug("Overwriting the 'reboot' method in target") 230 target.reboot = types.MethodType(_overwritten_reboot, target) 231 232 # Call the general update_from_target implementation 233 super(Gem5SimulationPlatform, self).update_from_target(target) 234 235 def gem5_capture_screen(self, filepath): 236 file_list = os.listdir(self.gem5_out_dir) 237 screen_caps = [] 238 for f in file_list: 239 if '.bmp' in f: 240 screen_caps.append(f) 241 242 successful_capture = False 243 if len(screen_caps) == 1: 244 # Bail out if we do not have image, and resort to the slower, built 245 # in method. 246 try: 247 import Image 248 gem5_image = os.path.join(self.gem5_out_dir, screen_caps[0]) 249 temp_image = os.path.join(self.gem5_out_dir, "file.png") 250 im = Image.open(gem5_image) 251 im.save(temp_image, "PNG") 252 shutil.copy(temp_image, filepath) 253 os.remove(temp_image) 254 gem5_logger.info("capture_screen: using gem5 screencap") 255 successful_capture = True 256 257 except (shutil.Error, ImportError, IOError): 258 pass 259 260 return successful_capture 261 262 def _deploy_m5(self, target): 263 # m5 is not yet installed so install it 264 host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, 265 target.abi, 'm5') 266 return target.install(host_executable) 267 268 def _resize_shell(self, target): 269 """ 270 Resize the shell to avoid line wrapping issues. 271 272 """ 273 # Try and avoid line wrapping as much as possible. 274 target.execute('{} stty columns 1024'.format(target.busybox)) 275 target.execute('reset', check_exit_code=False) 276 277# Methods that will be monkey-patched onto the target 278def _overwritten_reset(self): 279 raise TargetError('Resetting is not allowed on gem5 platforms!') 280 281def _overwritten_reboot(self): 282 raise TargetError('Rebooting is not allowed on gem5 platforms!') 283 284def _overwritten_capture_screen(self, filepath): 285 connection_screencapped = self.platform.gem5_capture_screen(filepath) 286 if connection_screencapped == False: 287 # The connection was not able to capture the screen so use the target 288 # implementation 289 self.logger.debug('{} was not able to screen cap, using the original target implementation'.format(self.platform.__class__.__name__)) 290 self.target_impl_capture_screen(filepath) 291 292 293