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