blob: 922187c8529d62b8746e48749363d4a0b2b2e9e6 [file] [log] [blame]
Yong Hong065423e2019-03-13 16:33:11 +08001# Copyright 2019 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
5import re
6
Yong Hong065423e2019-03-13 16:33:11 +08007from cros.factory.gooftool import common as gooftool_common
8from cros.factory.utils import type_utils
9
10
11# Path to the relied `gsctool` command line utility.
12GSCTOOL_PATH = '/usr/sbin/gsctool'
13
14
15class FirmwareVersion(type_utils.Obj):
16 def __init__(self, ro_version, rw_version):
17 super(FirmwareVersion, self).__init__(ro_version=ro_version,
18 rw_version=rw_version)
19
20class ImageInfo(type_utils.Obj):
21 def __init__(self, ro_fw_version, rw_fw_version, board_id_flags):
22 super(ImageInfo, self).__init__(ro_fw_version=ro_fw_version,
23 rw_fw_version=rw_fw_version,
24 board_id_flags=board_id_flags)
25
Yong Hong3f4e79a2019-03-28 17:45:25 +080026class BoardID(type_utils.Obj):
27 def __init__(self, type_, flags):
28 super(BoardID, self).__init__(type=type_, flags=flags)
29
Yong Hong065423e2019-03-13 16:33:11 +080030
31UpdateResult = type_utils.Enum(['NOOP', 'ALL_UPDATED', 'RW_UPDATED'])
32
33
34class GSCToolError(Exception):
35 pass
36
37
Fei Shaobd07c9a2020-06-15 19:04:50 +080038class GSCTool:
Yong Hong065423e2019-03-13 16:33:11 +080039 """Helper class to operate on Cr50 firmware by the `gsctool` cmdline utility.
40 """
41
42 def __init__(self, shell=None):
43 self._shell = shell or gooftool_common.Shell
44
45 def GetCr50FirmwareVersion(self):
46 """Get the version of the current Cr50 firmware.
47
48 Returns:
49 Instance of `FirmwareVersion`.
50
51 Raises:
52 `GSCToolError` if fails.
53 """
54 cmd = [GSCTOOL_PATH, '-M', '-a', '-f']
55 return self._GetAttrs(cmd, FirmwareVersion, {'RO_FW_VER': 'ro_version',
56 'RW_FW_VER': 'rw_version'},
57 'firmware versions.')
58
Cheng-Han Yang66066152019-12-16 16:21:59 +080059 def UpdateCr50Firmware(self, image_file, upstart_mode=True):
Yong Hong065423e2019-03-13 16:33:11 +080060 """Update the Cr50 firmware.
61
62 Args:
63 image_file: Path to the image file that contains the cr50 firmware image.
Cheng-Han Yang66066152019-12-16 16:21:59 +080064 upstart_mode: Use upstart mode.
Yong Hong065423e2019-03-13 16:33:11 +080065
66 Returns:
67 Enum element of `UpdateResult` if succeeds.
68
69 Raises:
70 `GSCToolError` if update fails.
71 """
Cheng-Han Yang66066152019-12-16 16:21:59 +080072 if upstart_mode:
73 cmd = [GSCTOOL_PATH, '-a', '-u', image_file]
74 # 0: noop. 1: all_updated, 2: rw_updated, 3: update_error
75 # See platform/ec/extra/usb_updater/gsctool.h for more detail.
76 cmd_result_checker = lambda result: 0 <= result.status <= 2
77 cmd_result = self._InvokeCommand(cmd, 'Failed to update Cr50 firmware',
78 cmd_result_checker=cmd_result_checker)
79 return {0: UpdateResult.NOOP,
80 1: UpdateResult.ALL_UPDATED,
81 2: UpdateResult.RW_UPDATED}[cmd_result.status]
82
83 cmd = [GSCTOOL_PATH, '-a', image_file]
84 self._InvokeCommand(cmd, 'Failed to update Cr50 firmware')
85 # The command will trigger a reboot, so the following lines should not be
86 # run.
87 raise GSCToolError("Device doesn't reboot after updating Cr50 firmware.")
Yong Hong065423e2019-03-13 16:33:11 +080088
89 def GetImageInfo(self, image_file):
90 """Get the version and the board id of the specified Cr50 firmware image.
91
92 Args:
93 image_file: Path to the Cr50 firmware image file.
94
95 Returns:
96 Instance of `ImageVersion`.
97
98 Raises:
99 `GSCToolError` if fails.
100 """
101 cmd = [GSCTOOL_PATH, '-M', '-b', image_file]
102 info = self._GetAttrs(cmd, ImageInfo, {'IMAGE_RO_FW_VER': 'ro_fw_version',
103 'IMAGE_RW_FW_VER': 'rw_fw_version',
104 'IMAGE_BID_FLAGS': 'board_id_flags'},
105 'image versions.')
106 # pylint: disable=attribute-defined-outside-init
107 info.board_id_flags = int(info.board_id_flags, 16)
108 return info
109
110 def _GetAttrs(self, cmd, AttrClass, fields, target_name):
Cheng-Han Yang66066152019-12-16 16:21:59 +0800111 cmd_result = self._InvokeCommand(cmd, 'failed to get %s' % target_name)
Yong Hong065423e2019-03-13 16:33:11 +0800112
113 translated_kwargs = {}
114 for line in cmd_result.stdout.splitlines():
115 line = line.strip()
Yilin Yang879fbda2020-05-14 13:52:30 +0800116 for field_name, attr_name in fields.items():
Yong Hong065423e2019-03-13 16:33:11 +0800117 if line.startswith(field_name + '='):
118 translated_kwargs[attr_name] = line[len(field_name) + 1:]
Yilin Yang879fbda2020-05-14 13:52:30 +0800119 missing_fields = [field_name for field_name, attr_name in fields.items()
Yong Hong065423e2019-03-13 16:33:11 +0800120 if attr_name not in translated_kwargs]
121 if missing_fields:
122 raise GSCToolError('%r Field(s) are missing, gsctool stdout=%r' %
123 (missing_fields, cmd_result.stdout))
124
125 return AttrClass(**translated_kwargs)
126
127 def SetFactoryMode(self, enable):
128 """Enable or disable the cr50 factory mode.
129
130 Args:
131 enable: `True` to enable the factory mode; `False` to disable the
132 factory mode.
133
134 Raises:
135 `GSCToolError` if fails.
136 """
137 enable_str = 'enable' if enable else 'disable'
138 cmd = [GSCTOOL_PATH, '-a', '-F', enable_str]
139 self._InvokeCommand(
Cheng-Han Yang66066152019-12-16 16:21:59 +0800140 cmd, 'failed to %s cr50 factory mode' % enable_str)
Yong Hong065423e2019-03-13 16:33:11 +0800141
142 def IsFactoryMode(self):
143 """Queries if the cr50 is in factory mode or not.
144
145 Returns:
146 `True` if it's in factory mode.
147
148 Raises:
149 `GSCToolError` if fails.
150 """
151 result = self._InvokeCommand([GSCTOOL_PATH, '-a', '-I'],
152 'getting ccd info fails in cr50')
153
154 # The pattern of output is as below in case of factory mode enabled:
155 # State: Locked
156 # Password: None
157 # Flags: 000000
158 # Capabilities, current and default:
159 # ...
160 # Capabilities are modified.
161 #
162 # If factory mode is disabed then the last line would be
163 # Capabilities are default.
164 return bool(re.search('^Capabilities are modified.$', result.stdout,
165 re.MULTILINE))
166
Yong Hong3f4e79a2019-03-28 17:45:25 +0800167 def GetBoardID(self):
168 """Get the board ID of the Cr50 firmware.
169
170 Returns:
171 Instance of `BoardID`.
172
173 Raises:
174 `GSCToolError` if fails.
175 """
176 _BID_TYPE_MASK = 0xffffffff
177
178 result = self._GetAttrs(
179 [GSCTOOL_PATH, '-a', '-M', '-i'], type_utils.Obj,
180 {k: k for k in ('BID_TYPE', 'BID_TYPE_INV', 'BID_FLAGS', 'BID_RLZ')},
181 'board ID')
182 if result.BID_RLZ == '????':
183 rlz_num = 0xffffffff
184 result.BID_RLZ = None
185 elif re.match(r'[A-Z]{4}$', result.BID_RLZ):
Yilin Yang99bfe2e2019-12-06 16:23:22 +0800186 rlz_num = int.from_bytes(result.BID_RLZ.encode('utf-8'), 'big')
Yong Hong3f4e79a2019-03-28 17:45:25 +0800187 else:
188 raise GSCToolError('Unexpected RLZ format: %r.' % result.BID_RLZ)
189 try:
190 bid_type = int(result.BID_TYPE, 16)
191 bid_type_inv = int(result.BID_TYPE_INV, 16)
192 bid_flags = int(result.BID_FLAGS, 16)
193 except Exception as e:
194 raise GSCToolError(e)
195
196 # The output of the gsctool command contains 4 fields, check if they are
197 # not conflicted to each other.
198 is_bid_type_programmed = (bid_type != _BID_TYPE_MASK or
199 bid_type_inv != _BID_TYPE_MASK)
200 is_bid_type_complement = ((bid_type & bid_type_inv) == 0 and
201 (bid_type | bid_type_inv) == _BID_TYPE_MASK)
202 if is_bid_type_programmed and not is_bid_type_complement:
203 raise GSCToolError('BID_TYPE(%x) and BID_TYPE_INV(%x) are not complement '
204 'to each other' % (bid_type, bid_type_inv))
205 if rlz_num != bid_type:
206 raise GSCToolError('BID_TYPE(%x) and RLZ_CODE(%s) mismatch.' %
207 (bid_type, result.BID_RLZ))
208 return BoardID(bid_type, bid_flags)
209
Yong Hong065423e2019-03-13 16:33:11 +0800210 def _InvokeCommand(self, cmd, failure_msg, cmd_result_checker=None):
211 cmd_result_checker = cmd_result_checker or (lambda result: result.success)
212 result = self._shell(cmd)
213 if not cmd_result_checker(result):
214 raise GSCToolError(failure_msg + ' (command result: %r)' % result)
215 return result