• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""A utility to program Chrome OS devices' firmware using servo.
6
7This utility expects the DUT to be connected to a servo device. This allows us
8to put the DUT into the required state and to actually program the DUT's
9firmware using FTDI, USB and/or serial interfaces provided by servo.
10
11Servo state is preserved across the programming process.
12"""
13
14import glob
15import logging
16import os
17import re
18import site
19import time
20import xml.etree.ElementTree
21
22from autotest_lib.client.common_lib import error
23from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
24
25
26# Number of seconds for program EC/BIOS to time out.
27FIRMWARE_PROGRAM_TIMEOUT_SEC = 600
28
29class ProgrammerError(Exception):
30    """Local exception class wrapper."""
31    pass
32
33
34class _BaseProgrammer(object):
35    """Class implementing base programmer services.
36
37    Private attributes:
38      _servo: a servo object controlling the servo device
39      _servo_host: a host object running commands like 'flashrom'
40      _servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
41                         listing servo controls and their required values for
42                         programming
43      _servo_prog_state_delay: time in second to wait after changing servo
44                               controls for programming.
45      _servo_saved_state: a list of the same elements as _servo_prog_state,
46                          those which need to be restored after programming
47      _program_cmd: a string, the shell command to run on the servo host
48                    to actually program the firmware. Dependent on
49                    firmware/hardware type, set by subclasses.
50    """
51
52    def __init__(self, servo, req_list, servo_host=None):
53        """Base constructor.
54        @param servo: a servo object controlling the servo device
55        @param req_list: a list of strings, names of the utilities required
56                         to be in the path for the programmer to succeed
57        @param servo_host: a host object to execute commands. Default to None,
58                           using the host object from the above servo object
59        """
60        self._servo = servo
61        self._servo_prog_state = ()
62        self._servo_prog_state_delay = 0
63        self._servo_saved_state = []
64        self._program_cmd = ''
65        self._servo_host = servo_host
66        if self._servo_host is None:
67            self._servo_host = self._servo._servo_host
68
69        try:
70            self._servo_host.run('which %s' % ' '.join(req_list))
71        except error.AutoservRunError:
72            # TODO: We turn this exception into a warn since the fw programmer
73            # is not working right now, and some systems do not package the
74            # required utilities its checking for.
75            # We should reinstate this exception once the programmer is working
76            # to indicate the missing utilities earlier in the test cycle.
77            # Bug chromium:371011 filed to track this.
78            logging.warn("Ignoring exception when verify required bins : %s",
79                         ' '.join(req_list))
80
81
82    def _set_servo_state(self):
83        """Set servo for programming, while saving the current state."""
84        logging.debug("Setting servo state for programming")
85        for item in self._servo_prog_state:
86            key, value = item.split(':')
87            try:
88                present = self._servo.get(key)
89            except error.TestFail:
90                logging.warn('Missing servo control: %s', key)
91                continue
92            if present != value:
93                self._servo_saved_state.append('%s:%s' % (key, present))
94            self._servo.set(key, value)
95        time.sleep(self._servo_prog_state_delay)
96
97
98    def _restore_servo_state(self):
99        """Restore previously saved servo state."""
100        logging.debug("Restoring servo state after programming")
101        self._servo_saved_state.reverse()  # Do it in the reverse order.
102        for item in self._servo_saved_state:
103            key, value = item.split(':')
104            self._servo.set(key, value)
105
106
107    def program(self):
108        """Program the firmware as configured by a subclass."""
109        self._set_servo_state()
110        try:
111            logging.debug("Programmer command: %s", self._program_cmd)
112            self._servo_host.run(self._program_cmd,
113                                 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
114        finally:
115            self._restore_servo_state()
116
117
118class FlashromProgrammer(_BaseProgrammer):
119    """Class for programming AP flashrom."""
120
121    def __init__(self, servo, keep_ro=False):
122        """Configure required servo state.
123
124        @param servo: a servo object controlling the servo device
125        @param keep_ro: True to keep the RO portion unchanged
126        """
127        super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
128        self._keep_ro = keep_ro
129        self._fw_path = None
130        self._tmp_path = '/tmp'
131        self._fw_main = os.path.join(self._tmp_path, 'fw_main')
132        self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
133        self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
134        self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
135        self._gbb = os.path.join(self._tmp_path, 'gbb')
136        self._servo_version = self._servo.get_servo_version()
137        self._servo_serials = self._servo._server.get_servo_serials()
138
139
140    def program(self):
141        """Program the firmware but preserve VPD and HWID."""
142        assert self._fw_path is not None
143        self._set_servo_state()
144        try:
145            wp_ro_section = [('WP_RO', self._wp_ro)]
146            rw_vpd_section = [('RW_VPD', self._rw_vpd)]
147            ro_vpd_section = [('RO_VPD', self._ro_vpd)]
148            gbb_section = [('GBB', self._gbb)]
149            if self._keep_ro:
150                # Keep the whole RO portion
151                preserved_sections = wp_ro_section + rw_vpd_section
152            else:
153                preserved_sections = ro_vpd_section + rw_vpd_section
154
155            servo_v2_programmer = 'ft2232_spi:type=servo-v2'
156            servo_v3_programmer = 'linux_spi'
157            servo_v4_with_micro_programmer = 'raiden_spi'
158            servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP'
159            if self._servo_version == 'servo_v2':
160                programmer = servo_v2_programmer
161                servo_serial = self._servo_serials.get('main')
162                if servo_serial:
163                    programmer += ',serial=%s' % servo_serial
164            elif self._servo_version == 'servo_v3':
165                programmer = servo_v3_programmer
166            elif self._servo_version == 'servo_v4_with_servo_micro':
167                # When a uServo is connected to a DUT with CCD support, the
168                # firmware programmer will always use the uServo to program.
169                servo_micro_serial = self._servo_serials.get('servo_micro')
170                programmer = servo_v4_with_micro_programmer
171                programmer += ',serial=%s' % servo_micro_serial
172            elif self._servo_version == 'servo_v4_with_ccd_cr50':
173                ccd_serial = self._servo_serials.get('ccd')
174                programmer = servo_v4_with_ccd_programmer
175                programmer += ',serial=%s' % ccd_serial
176            else:
177                raise Exception('Servo version %s is not supported.' %
178                                self._servo_version)
179            # Save needed sections from current firmware
180            for section in preserved_sections + gbb_section:
181                self._servo_host.run(' '.join([
182                    'flashrom', '-V', '-p', programmer,
183                    '-r', self._fw_main, '-i', '%s:%s' % section]),
184                    timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
185
186            # Pack the saved VPD into new firmware
187            self._servo_host.run('cp %s %s' % (self._fw_path, self._fw_main))
188            img_size = self._servo_host.run_output(
189                    "stat -c '%%s' %s" % self._fw_main)
190            pack_cmd = ['flashrom',
191                    '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
192                        self._fw_main, img_size),
193                    '-w', self._fw_main]
194            for section in preserved_sections:
195                pack_cmd.extend(['-i', '%s:%s' % section])
196            self._servo_host.run(' '.join(pack_cmd),
197                                 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
198
199            # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
200            if not self._keep_ro:
201                # Read original HWID. The output format is:
202                #    hardware_id: RAMBI TEST A_A 0128
203                gbb_hwid_output = self._servo_host.run_output(
204                        'gbb_utility -g --hwid %s' % self._gbb)
205                original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
206
207                # Write HWID to new firmware
208                self._servo_host.run("gbb_utility -s --hwid='%s' %s" %
209                        (original_hwid, self._fw_main))
210
211            # Flash the new firmware
212            self._servo_host.run(' '.join([
213                    'flashrom', '-V', '-p', programmer,
214                    '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
215        finally:
216            self._servo.get_power_state_controller().reset()
217            self._restore_servo_state()
218
219
220    def prepare_programmer(self, path):
221        """Prepare programmer for programming.
222
223        @param path: a string, name of the file containing the firmware image.
224        """
225        self._fw_path = path
226        # CCD takes care holding AP/EC. Don't need the following steps.
227        if self._servo_version != 'servo_v4_with_ccd_cr50':
228            faft_config = FAFTConfig(self._servo.get_board())
229            self._servo_prog_state_delay = faft_config.servo_prog_state_delay
230            self._servo_prog_state = (
231                'spi2_vref:%s' % faft_config.spi_voltage,
232                'spi2_buf_en:on',
233                'spi2_buf_on_flex_en:on',
234                'spi_hold:off',
235                'cold_reset:on',
236                'usbpd_reset:on',
237                )
238
239
240class FlashECProgrammer(_BaseProgrammer):
241    """Class for programming AP flashrom."""
242
243    def __init__(self, servo, host=None, ec_chip=None):
244        """Configure required servo state.
245
246        @param servo: a servo object controlling the servo device
247        @param host: a host object to execute commands. Default to None,
248                     using the host object from the above servo object, i.e.
249                     a servo host. A CrOS host object can be passed here
250                     such that it executes commands on the CrOS device.
251        @param ec_chip: a string of EC chip. Default to None, using the
252                        EC chip name reported by servo, the primary EC.
253                        Can pass a different chip name, for the case of
254                        the base EC.
255
256        """
257        super(FlashECProgrammer, self).__init__(servo, ['flash_ec'], host)
258        self._servo_version = self._servo.get_servo_version()
259        if ec_chip is None:
260            self._ec_chip = servo.get('ec_chip')
261        else:
262            self._ec_chip = ec_chip
263
264    def prepare_programmer(self, image):
265        """Prepare programmer for programming.
266
267        @param image: string with the location of the image file
268        """
269        # Get the port of servod. flash_ec may use it to talk to servod.
270        port = self._servo._servo_host.servo_port
271        self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
272                             (self._ec_chip, image, port))
273        if self._servo_version == 'servo_v4_with_ccd_cr50':
274            self._program_cmd += ' --raiden'
275
276
277class ProgrammerV2(object):
278    """Main programmer class which provides programmer for BIOS and EC with
279    servo V2."""
280
281    def __init__(self, servo):
282        self._servo = servo
283        self._valid_boards = self._get_valid_v2_boards()
284        self._bios_programmer = self._factory_bios(self._servo)
285        self._ec_programmer = self._factory_ec(self._servo)
286
287
288    @staticmethod
289    def _get_valid_v2_boards():
290        """Greps servod config files to look for valid v2 boards.
291
292        @return A list of valid board names.
293        """
294        site_packages_paths = site.getsitepackages()
295        SERVOD_CONFIG_DATA_DIR = None
296        for p in site_packages_paths:
297            servo_data_path = os.path.join(p, 'servo', 'data')
298            if os.path.exists(servo_data_path):
299                SERVOD_CONFIG_DATA_DIR = servo_data_path
300                break
301        if not SERVOD_CONFIG_DATA_DIR:
302            raise ProgrammerError(
303                    'Unable to locate data directory of Python servo module')
304        SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
305        SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
306        SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
307
308        def is_v2_compatible_board(board_config_path):
309            """Check if the given board config file is v2-compatible.
310
311            @param board_config_path: Path to a board config XML file.
312
313            @return True if the board is v2-compatible; False otherwise.
314            """
315            configs = []
316            def get_all_includes(config_path):
317                """Get all included XML config names in the given config file.
318
319                @param config_path: Path to a servo config file.
320                """
321                root = xml.etree.ElementTree.parse(config_path).getroot()
322                for element in root.findall('include'):
323                    include_name = element.find('name').text
324                    configs.append(include_name)
325                    get_all_includes(os.path.join(
326                            SERVOD_CONFIG_DATA_DIR, include_name))
327
328            get_all_includes(board_config_path)
329            return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
330
331        result = []
332        board_overlays = glob.glob(
333                os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
334        for overlay_path in board_overlays:
335            if is_v2_compatible_board(overlay_path):
336                result.append(re.search(SERVO_CONFIG_REGEXP,
337                                        overlay_path).group('board'))
338        return result
339
340
341    def _get_flashrom_programmer(self, servo):
342        """Gets a proper flashrom programmer.
343
344        @param servo: A servo object.
345
346        @return A programmer for flashrom.
347        """
348        return FlashromProgrammer(servo)
349
350
351    def _factory_bios(self, servo):
352        """Instantiates and returns (bios, ec) programmers for the board.
353
354        @param servo: A servo object.
355
356        @return A programmer for ec. If the programmer is not supported
357            for the board, None will be returned.
358        """
359        _bios_prog = None
360        _board = servo.get_board()
361
362        logging.debug('Setting up BIOS programmer for board: %s', _board)
363        if _board in self._valid_boards:
364            _bios_prog = self._get_flashrom_programmer(servo)
365        else:
366            logging.warning('No BIOS programmer found for board: %s', _board)
367
368        return _bios_prog
369
370
371    def _factory_ec(self, servo):
372        """Instantiates and returns ec programmer for the board.
373
374        @param servo: A servo object.
375
376        @return A programmer for ec. If the programmer is not supported
377            for the board, None will be returned.
378        """
379        _ec_prog = None
380        _board = servo.get_board()
381
382        logging.debug('Setting up EC programmer for board: %s', _board)
383        if _board in self._valid_boards:
384            _ec_prog = FlashECProgrammer(servo)
385        else:
386            logging.warning('No EC programmer found for board: %s', _board)
387
388        return _ec_prog
389
390
391    def program_bios(self, image):
392        """Programs the DUT with provide bios image.
393
394        @param image: (required) location of bios image file.
395
396        """
397        self._bios_programmer.prepare_programmer(image)
398        self._bios_programmer.program()
399
400
401    def program_ec(self, image):
402        """Programs the DUT with provide ec image.
403
404        @param image: (required) location of ec image file.
405
406        """
407        self._ec_programmer.prepare_programmer(image)
408        self._ec_programmer.program()
409
410
411class ProgrammerV2RwOnly(ProgrammerV2):
412    """Main programmer class which provides programmer for only updating the RW
413    portion of BIOS with servo V2.
414
415    It does nothing on EC, as EC software sync on the next boot will
416    automatically overwrite the EC RW portion, using the EC RW image inside
417    the BIOS RW image.
418
419    """
420
421    def _get_flashrom_programmer(self, servo):
422        """Gets a proper flashrom programmer.
423
424        @param servo: A servo object.
425
426        @return A programmer for flashrom.
427        """
428        return FlashromProgrammer(servo, keep_ro=True)
429
430
431    def program_ec(self, image):
432        """Programs the DUT with provide ec image.
433
434        @param image: (required) location of ec image file.
435
436        """
437        # Do nothing. EC software sync will update the EC RW.
438        pass
439
440
441class ProgrammerV3(object):
442    """Main programmer class which provides programmer for BIOS and EC with
443    servo V3.
444
445    Different from programmer for servo v2, programmer for servo v3 does not
446    try to validate if the board can use servo V3 to update firmware. As long as
447    the servod process running in beagblebone with given board, the program will
448    attempt to flash bios and ec.
449
450    """
451
452    def __init__(self, servo):
453        self._servo = servo
454        self._bios_programmer = FlashromProgrammer(servo)
455        self._ec_programmer = FlashECProgrammer(servo)
456
457
458    def program_bios(self, image):
459        """Programs the DUT with provide bios image.
460
461        @param image: (required) location of bios image file.
462
463        """
464        self._bios_programmer.prepare_programmer(image)
465        self._bios_programmer.program()
466
467
468    def program_ec(self, image):
469        """Programs the DUT with provide ec image.
470
471        @param image: (required) location of ec image file.
472
473        """
474        self._ec_programmer.prepare_programmer(image)
475        self._ec_programmer.program()
476
477
478class ProgrammerV3RwOnly(ProgrammerV3):
479    """Main programmer class which provides programmer for only updating the RW
480    portion of BIOS with servo V3.
481
482    It does nothing on EC, as EC software sync on the next boot will
483    automatically overwrite the EC RW portion, using the EC RW image inside
484    the BIOS RW image.
485
486    """
487
488    def __init__(self, servo):
489        self._servo = servo
490        self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
491
492
493    def program_ec(self, image):
494        """Programs the DUT with provide ec image.
495
496        @param image: (required) location of ec image file.
497
498        """
499        # Do nothing. EC software sync will update the EC RW.
500        pass
501
502
503class ProgrammerDfu(object):
504    """Main programmer class which provides programmer for Base EC via DFU.
505
506    It programs through the DUT, i.e. running the flash_ec script on DUT
507    instead of running it in beaglebone (a host of the servo board).
508    It is independent of the version of servo board as long as the servo
509    board has the ec_boot_mode interface.
510
511    """
512
513    def __init__(self, servo, cros_host):
514        self._servo = servo
515        self._cros_host = cros_host
516        # Get the chip name of the base EC and append '_dfu' to it, like
517        # 'stm32' -> 'stm32_dfu'.
518        ec_chip = servo.get(servo.get_base_board() + '_ec_chip') + '_dfu'
519        self._ec_programmer = FlashECProgrammer(servo, cros_host, ec_chip)
520
521
522    def program_ec(self, image):
523        """Programs the DUT with provide ec image.
524
525        @param image: (required) location of ec image file.
526
527        """
528        self._ec_programmer.prepare_programmer(image)
529        ec_boot_mode = self._servo.get_base_board() + '_ec_boot_mode'
530        try:
531            self._servo.set(ec_boot_mode, 'on')
532            # Power cycle the base to enter DFU mode
533            self._cros_host.run('ectool gpioset PP3300_DX_BASE 0')
534            self._cros_host.run('ectool gpioset PP3300_DX_BASE 1')
535            self._ec_programmer.program()
536        finally:
537            self._servo.set(ec_boot_mode, 'off')
538            # Power cycle the base to back normal mode
539            self._cros_host.run('ectool gpioset PP3300_DX_BASE 0')
540            self._cros_host.run('ectool gpioset PP3300_DX_BASE 1')
541