Home | History | Annotate | Download | only in module
      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