1 # 2 # Copyright 2015 ARM Limited 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 # 16 import os 17 import time 18 import tarfile 19 import shutil 20 21 from devlib.module import HardRestModule, BootModule, FlashModule 22 from devlib.exception import TargetError, HostError 23 from devlib.utils.serial_port import open_serial_connection, pulse_dtr, write_characters 24 from devlib.utils.uefi import UefiMenu, UefiConfig 25 from devlib.utils.uboot import UbootMenu 26 27 28 AUTOSTART_MESSAGE = 'Press Enter to stop auto boot...' 29 POWERUP_MESSAGE = 'Powering up system...' 30 DEFAULT_MCC_PROMPT = 'Cmd>' 31 32 33 class VexpressDtrHardReset(HardRestModule): 34 35 name = 'vexpress-dtr' 36 stage = 'early' 37 38 @staticmethod 39 def probe(target): 40 return True 41 42 def __init__(self, target, port='/dev/ttyS0', baudrate=115200, 43 mcc_prompt=DEFAULT_MCC_PROMPT, timeout=300): 44 super(VexpressDtrHardReset, self).__init__(target) 45 self.port = port 46 self.baudrate = baudrate 47 self.mcc_prompt = mcc_prompt 48 self.timeout = timeout 49 50 def __call__(self): 51 try: 52 if self.target.is_connected: 53 self.target.execute('sync') 54 except TargetError: 55 pass 56 with open_serial_connection(port=self.port, 57 baudrate=self.baudrate, 58 timeout=self.timeout, 59 init_dtr=0, 60 get_conn=True) as (_, conn): 61 pulse_dtr(conn, state=True, duration=0.1) # TRM specifies a pulse of >=100ms 62 63 64 class VexpressReboottxtHardReset(HardRestModule): 65 66 name = 'vexpress-reboottxt' 67 stage = 'early' 68 69 @staticmethod 70 def probe(target): 71 return True 72 73 def __init__(self, target, 74 port='/dev/ttyS0', baudrate=115200, 75 path='/media/VEMSD', 76 mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1): 77 super(VexpressReboottxtHardReset, self).__init__(target) 78 self.port = port 79 self.baudrate = baudrate 80 self.path = path 81 self.mcc_prompt = mcc_prompt 82 self.timeout = timeout 83 self.short_delay = short_delay 84 self.filepath = os.path.join(path, 'reboot.txt') 85 86 def __call__(self): 87 try: 88 if self.target.is_connected: 89 self.target.execute('sync') 90 except TargetError: 91 pass 92 93 if not os.path.exists(self.path): 94 self.logger.debug('{} does not exisit; attempting to mount...'.format(self.path)) 95 with open_serial_connection(port=self.port, 96 baudrate=self.baudrate, 97 timeout=self.timeout, 98 init_dtr=0) as tty: 99 wait_for_vemsd(self.path, tty, self.mcc_prompt, self.short_delay) 100 with open(self.filepath, 'w'): 101 pass 102 103 104 class VexpressBootModule(BootModule): 105 106 stage = 'early' 107 108 @staticmethod 109 def probe(target): 110 return True 111 112 def __init__(self, target, uefi_entry=None, 113 port='/dev/ttyS0', baudrate=115200, 114 mcc_prompt=DEFAULT_MCC_PROMPT, 115 timeout=120, short_delay=1): 116 super(VexpressBootModule, self).__init__(target) 117 self.port = port 118 self.baudrate = baudrate 119 self.uefi_entry = uefi_entry 120 self.mcc_prompt = mcc_prompt 121 self.timeout = timeout 122 self.short_delay = short_delay 123 124 def __call__(self): 125 with open_serial_connection(port=self.port, 126 baudrate=self.baudrate, 127 timeout=self.timeout, 128 init_dtr=0) as tty: 129 self.get_through_early_boot(tty) 130 self.perform_boot_sequence(tty) 131 self.wait_for_android_prompt(tty) 132 133 def perform_boot_sequence(self, tty): 134 raise NotImplementedError() 135 136 def get_through_early_boot(self, tty): 137 self.logger.debug('Establishing initial state...') 138 tty.sendline('') 139 i = tty.expect([AUTOSTART_MESSAGE, POWERUP_MESSAGE, self.mcc_prompt]) 140 if i == 2: 141 self.logger.debug('Saw MCC prompt.') 142 time.sleep(self.short_delay) 143 tty.sendline('reboot') 144 elif i == 1: 145 self.logger.debug('Saw powering up message (assuming soft reboot).') 146 else: 147 self.logger.debug('Saw auto boot message.') 148 tty.sendline('') 149 time.sleep(self.short_delay) 150 tty.sendline('reboot') 151 152 def get_uefi_menu(self, tty): 153 menu = UefiMenu(tty) 154 self.logger.debug('Waiting for UEFI menu...') 155 menu.wait(timeout=self.timeout) 156 return menu 157 158 def wait_for_android_prompt(self, tty): 159 self.logger.debug('Waiting for the Android prompt.') 160 tty.expect(self.target.shell_prompt, timeout=self.timeout) 161 # This delay is needed to allow the platform some time to finish 162 # initilizing; querying the ip address too early from connect() may 163 # result in a bogus address being assigned to eth0. 164 time.sleep(5) 165 166 167 class VexpressUefiBoot(VexpressBootModule): 168 169 name = 'vexpress-uefi' 170 171 def __init__(self, target, uefi_entry, 172 image, fdt, bootargs, initrd, 173 *args, **kwargs): 174 super(VexpressUefiBoot, self).__init__(target, uefi_entry=uefi_entry, 175 *args, **kwargs) 176 self.uefi_config = self._create_config(image, fdt, bootargs, initrd) 177 178 def perform_boot_sequence(self, tty): 179 menu = self.get_uefi_menu(tty) 180 try: 181 menu.select(self.uefi_entry) 182 except LookupError: 183 self.logger.debug('{} UEFI entry not found.'.format(self.uefi_entry)) 184 self.logger.debug('Attempting to create one using default flasher configuration.') 185 menu.create_entry(self.uefi_entry, self.uefi_config) 186 menu.select(self.uefi_entry) 187 188 def _create_config(self, image, fdt, bootargs, initrd): # pylint: disable=R0201 189 config_dict = { 190 'image_name': image, 191 'image_args': bootargs, 192 'initrd': initrd, 193 } 194 195 if fdt: 196 config_dict['fdt_support'] = True 197 config_dict['fdt_path'] = fdt 198 else: 199 config_dict['fdt_support'] = False 200 201 return UefiConfig(config_dict) 202 203 204 class VexpressUefiShellBoot(VexpressBootModule): 205 206 name = 'vexpress-uefi-shell' 207 208 def __init__(self, target, uefi_entry='^Shell$', 209 efi_shell_prompt='Shell>', 210 image='kernel', bootargs=None, 211 *args, **kwargs): 212 super(VexpressUefiShellBoot, self).__init__(target, uefi_entry=uefi_entry, 213 *args, **kwargs) 214 self.efi_shell_prompt = efi_shell_prompt 215 self.image = image 216 self.bootargs = bootargs 217 218 def perform_boot_sequence(self, tty): 219 menu = self.get_uefi_menu(tty) 220 try: 221 menu.select(self.uefi_entry) 222 except LookupError: 223 raise TargetError('Did not see "{}" UEFI entry.'.format(self.uefi_entry)) 224 tty.expect(self.efi_shell_prompt, timeout=self.timeout) 225 if self.bootargs: 226 tty.sendline('') # stop default boot 227 time.sleep(self.short_delay) 228 efi_shell_command = '{} {}'.format(self.image, self.bootargs) 229 self.logger.debug(efi_shell_command) 230 write_characters(tty, efi_shell_command) 231 tty.sendline('\r\n') 232 233 234 class VexpressUBoot(VexpressBootModule): 235 236 name = 'vexpress-u-boot' 237 238 def __init__(self, target, env=None, 239 *args, **kwargs): 240 super(VexpressUBoot, self).__init__(target, *args, **kwargs) 241 self.env = env 242 243 def perform_boot_sequence(self, tty): 244 if self.env is None: 245 return # Will boot automatically 246 247 menu = UbootMenu(tty) 248 self.logger.debug('Waiting for U-Boot prompt...') 249 menu.open(timeout=120) 250 for var, value in self.env.iteritems(): 251 menu.setenv(var, value) 252 menu.boot() 253 254 255 class VexpressBootmon(VexpressBootModule): 256 257 name = 'vexpress-bootmon' 258 259 def __init__(self, target, 260 image, fdt, initrd, bootargs, 261 uses_bootscript=False, 262 bootmon_prompt='>', 263 *args, **kwargs): 264 super(VexpressBootmon, self).__init__(target, *args, **kwargs) 265 self.image = image 266 self.fdt = fdt 267 self.initrd = initrd 268 self.bootargs = bootargs 269 self.uses_bootscript = uses_bootscript 270 self.bootmon_prompt = bootmon_prompt 271 272 def perform_boot_sequence(self, tty): 273 if self.uses_bootscript: 274 return # Will boot automatically 275 276 time.sleep(self.short_delay) 277 tty.expect(self.bootmon_prompt, timeout=self.timeout) 278 with open_serial_connection(port=self.port, 279 baudrate=self.baudrate, 280 timeout=self.timeout, 281 init_dtr=0) as tty: 282 write_characters(tty, 'fl linux fdt {}'.format(self.fdt)) 283 write_characters(tty, 'fl linux initrd {}'.format(self.initrd)) 284 write_characters(tty, 'fl linux boot {} {}'.format(self.image, 285 self.bootargs)) 286 287 288 class VersatileExpressFlashModule(FlashModule): 289 290 name = 'vexpress-vemsd' 291 description = """ 292 Enables flashing of kernels and firmware to ARM Versatile Express devices. 293 294 This modules enables flashing of image bundles or individual images to ARM 295 Versatile Express-based devices (e.g. JUNO) via host-mounted MicroSD on the 296 board. 297 298 The bundle, if specified, must reflect the directory structure of the MicroSD 299 and will be extracted directly into the location it is mounted on the host. The 300 images, if specified, must be a dict mapping the absolute path of the image on 301 the host to the destination path within the board's MicroSD; the destination path 302 may be either absolute, or relative to the MicroSD mount location. 303 304 """ 305 306 stage = 'early' 307 308 @staticmethod 309 def probe(target): 310 if not target.has('hard_reset'): 311 return False 312 return True 313 314 def __init__(self, target, vemsd_mount, mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1): 315 super(VersatileExpressFlashModule, self).__init__(target) 316 self.vemsd_mount = vemsd_mount 317 self.mcc_prompt = mcc_prompt 318 self.timeout = timeout 319 self.short_delay = short_delay 320 321 def __call__(self, image_bundle=None, images=None, bootargs=None): 322 self.target.hard_reset() 323 with open_serial_connection(port=self.target.platform.serial_port, 324 baudrate=self.target.platform.baudrate, 325 timeout=self.timeout, 326 init_dtr=0) as tty: 327 i = tty.expect([self.mcc_prompt, AUTOSTART_MESSAGE]) 328 if i: 329 tty.sendline('') 330 wait_for_vemsd(self.vemsd_mount, tty, self.mcc_prompt, self.short_delay) 331 try: 332 if image_bundle: 333 self._deploy_image_bundle(image_bundle) 334 if images: 335 self._overlay_images(images) 336 os.system('sync') 337 except (IOError, OSError), e: 338 msg = 'Could not deploy images to {}; got: {}' 339 raise TargetError(msg.format(self.vemsd_mount, e)) 340 self.target.boot() 341 self.target.connect(timeout=30) 342 343 def _deploy_image_bundle(self, bundle): 344 self.logger.debug('Validating {}'.format(bundle)) 345 validate_image_bundle(bundle) 346 self.logger.debug('Extracting {} into {}...'.format(bundle, self.vemsd_mount)) 347 with tarfile.open(bundle) as tar: 348 tar.extractall(self.vemsd_mount) 349 350 def _overlay_images(self, images): 351 for dest, src in images.iteritems(): 352 dest = os.path.join(self.vemsd_mount, dest) 353 self.logger.debug('Copying {} to {}'.format(src, dest)) 354 shutil.copy(src, dest) 355 356 357 # utility functions 358 359 def validate_image_bundle(bundle): 360 if not tarfile.is_tarfile(bundle): 361 raise HostError('Image bundle {} does not appear to be a valid TAR file.'.format(bundle)) 362 with tarfile.open(bundle) as tar: 363 try: 364 tar.getmember('config.txt') 365 except KeyError: 366 try: 367 tar.getmember('./config.txt') 368 except KeyError: 369 msg = 'Tarball {} does not appear to be a valid image bundle (did not see config.txt).' 370 raise HostError(msg.format(bundle)) 371 372 373 def wait_for_vemsd(vemsd_mount, tty, mcc_prompt=DEFAULT_MCC_PROMPT, short_delay=1, retries=3): 374 attempts = 1 + retries 375 path = os.path.join(vemsd_mount, 'config.txt') 376 if os.path.exists(path): 377 return 378 for _ in xrange(attempts): 379 tty.sendline('') # clear any garbage 380 tty.expect(mcc_prompt, timeout=short_delay) 381 tty.sendline('usb_on') 382 time.sleep(short_delay * 3) 383 if os.path.exists(path): 384 return 385 raise TargetError('Could not mount {}'.format(vemsd_mount)) 386 387