blob: 1860800733a1f2b8cbb9b9ab509cfd9bad06f14e [file] [log] [blame]
Tammo Spalink01e11722012-07-24 10:17:54 -07001# pylint: disable=W0201
Hung-Te Linb33e5642012-03-02 16:12:34 +08002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""ChromeOS Firmware Utilities
7
8This modules provides easy access to ChromeOS firmware.
9
10To access the contents of a firmware image, use FimwareImage().
11To access the flash chipset containing firmware, use Flashrom().
12To get the content of (cacheable) firmware, use LoadMainFirmware() or
13 LoadEcFirmware().
14"""
15
16import collections
17import logging
18import re
Hung-Te Lind44e1292017-02-15 11:34:26 +080019import tempfile
Hung-Te Linb33e5642012-03-02 16:12:34 +080020
Tammo Spalinka40293e2012-07-04 14:58:56 +080021import factory_common # pylint: disable=W0611
Hung-Te Lind44e1292017-02-15 11:34:26 +080022from cros.factory.gooftool import common
23from cros.factory.gooftool import fmap
Tammo Spalink9a96b8a2012-04-03 11:10:41 +080024
Hung-Te Linb33e5642012-03-02 16:12:34 +080025
Hung-Te Linb33e5642012-03-02 16:12:34 +080026# Names to select target bus.
27TARGET_MAIN = 'main'
28TARGET_EC = 'ec'
Ricky Liangfb3d01b2014-08-27 12:02:20 +080029TARGET_PD = 'pd'
Hung-Te Linb33e5642012-03-02 16:12:34 +080030
31# Types of named tuples
32WpStatus = collections.namedtuple('WpStatus', 'enabled offset size')
Hung-Te Linb33e5642012-03-02 16:12:34 +080033
34
35class Flashrom(object):
36 """Wrapper for calling system command flashrom(8)."""
37
38 # flashrom(8) command line parameters
Ricky Liangfb3d01b2014-08-27 12:02:20 +080039 _VALID_TARGETS = (TARGET_MAIN, TARGET_EC, TARGET_PD)
40 _TARGET_MAP = {
Hung-Te Lin56b18402015-01-16 14:52:30 +080041 TARGET_MAIN: '-p host',
42 TARGET_EC: '-p ec',
Hung-Te Lind3b124c2016-10-20 22:22:31 +080043 TARGET_PD: '-p ec:type=pd',
Ricky Liangfb3d01b2014-08-27 12:02:20 +080044 }
Hung-Te Lin56b18402015-01-16 14:52:30 +080045 _WRITE_FLAGS = '--fast-verify'
46 _READ_FLAGS = ''
Hung-Te Linb33e5642012-03-02 16:12:34 +080047
48 def __init__(self, target=None):
49 self._target = target or TARGET_MAIN
50
51 def _InvokeCommand(self, param, ignore_status=False):
52 command = ' '.join(['flashrom', self._TARGET_MAP[self._target], param])
53 logging.debug('Flashrom._InvokeCommand: %s', command)
Hung-Te Lind44e1292017-02-15 11:34:26 +080054 result = common.Shell(command)
Hung-Te Linb33e5642012-03-02 16:12:34 +080055 if not (ignore_status or result.success):
Hung-Te Lind44e1292017-02-15 11:34:26 +080056 raise IOError('Failed in command: %s\n%s' % (command, result.stderr))
Hung-Te Linb33e5642012-03-02 16:12:34 +080057 return result
58
59 def GetTarget(self):
60 """Gets current target (bus) to access."""
61 return self._target
62
63 def SetTarget(self, target):
64 """Sets current target (bus) to access."""
Hung-Te Lin56b18402015-01-16 14:52:30 +080065 assert target in self._VALID_TARGETS, 'Unknown target: %s' % target
Hung-Te Linb33e5642012-03-02 16:12:34 +080066 self._target = target
67
68 def GetSize(self):
Hung-Te Lin56b18402015-01-16 14:52:30 +080069 return int(self._InvokeCommand('--get-size').stdout.splitlines()[-1], 0)
Hung-Te Linb33e5642012-03-02 16:12:34 +080070
71 def GetName(self):
72 """Returns a key-value dict for chipset info, or None for any failure."""
Hung-Te Lin56b18402015-01-16 14:52:30 +080073 results = self._InvokeCommand('--flash-name', ignore_status=True).stdout
Hung-Te Linb33e5642012-03-02 16:12:34 +080074 match_list = re.findall(r'\b(\w+)="([^"]*)"', results)
75 return dict(match_list) if match_list else None
76
77 def Read(self, filename=None, sections=None):
78 """Reads whole image from selected flash chipset.
79
80 Args:
81 filename: File name to receive image. None to use temporary file.
82 sections: List of sections to read. None to read whole image.
83
84 Returns:
85 Image data read from flash chipset.
86 """
87 if filename is None:
Hung-Te Lind44e1292017-02-15 11:34:26 +080088 with tempfile.NamedTemporaryFile(prefix='fw_%s_' % self._target) as f:
Hung-Te Linb33e5642012-03-02 16:12:34 +080089 return self.Read(f.name)
90 sections_param = [name % '-i %s' for name in sections or []]
91 self._InvokeCommand("-r '%s' %s %s" % (filename, ' '.join(sections_param),
92 self._READ_FLAGS))
93 with open(filename, 'rb') as file_handle:
94 return file_handle.read()
95
96 def Write(self, data=None, filename=None, sections=None):
97 """Writes image into selected flash chipset.
98
99 Args:
100 data: Image data to write. None to write given file.
101 filename: File name of image to write if data is None.
102 sections: List of sections to write. None to write whole image.
103 """
104 assert (((data is not None) and (filename is None)) or
105 ((data is None) and (filename is not None))), \
Hung-Te Lin56b18402015-01-16 14:52:30 +0800106 'Either data or filename should be None.'
Hung-Te Linb33e5642012-03-02 16:12:34 +0800107 if data is not None:
Hung-Te Lind44e1292017-02-15 11:34:26 +0800108 with tempfile.NamedTemporaryFile(prefix='fw_%s_' % self._target) as f:
Hung-Te Linb33e5642012-03-02 16:12:34 +0800109 f.write(data)
110 f.flush()
111 return self.Write(None, f.name)
Hung-Te Lind44e1292017-02-15 11:34:26 +0800112 sections_param = [('-i %s' % name) for name in sections or []]
Hung-Te Linb33e5642012-03-02 16:12:34 +0800113 self._InvokeCommand("-w '%s' %s %s" % (filename, ' '.join(sections_param),
114 self._WRITE_FLAGS))
115
116 def GetWriteProtectionStatus(self):
117 """Gets write protection status from selected flash chipset.
118
119 Returns: A named tuple with (enabled, offset, size).
120 """
121 # flashrom(8) output: WP: status: 0x80
122 # WP: status.srp0: 1
123 # WP: write protect is %s. (disabled/enabled)
124 # WP: write protect range: start=0x%8x, len=0x%08x
Hung-Te Lin56b18402015-01-16 14:52:30 +0800125 results = self._InvokeCommand('--wp-status').stdout
Hung-Te Linb33e5642012-03-02 16:12:34 +0800126 status = re.findall(r'WP: write protect is (\w+)\.', results)
127 if len(status) != 1:
Hung-Te Lind44e1292017-02-15 11:34:26 +0800128 raise IOError('Failed getting write protection status')
Hung-Te Linb33e5642012-03-02 16:12:34 +0800129 status = status[0]
130 if status not in ('enabled', 'disabled'):
Hung-Te Lind44e1292017-02-15 11:34:26 +0800131 raise ValueError('Unknown write protection status: %s' % status)
Hung-Te Linb33e5642012-03-02 16:12:34 +0800132
133 wp_range = re.findall(r'WP: write protect range: start=(\w+), len=(\w+)',
134 results)
135 if len(wp_range) != 1:
Hung-Te Lind44e1292017-02-15 11:34:26 +0800136 raise IOError('Failed getting write protection range')
Hung-Te Linb33e5642012-03-02 16:12:34 +0800137 wp_range = wp_range[0]
138 return WpStatus(True if status == 'enabled' else False,
139 int(wp_range[0], 0),
140 int(wp_range[1], 0))
141
142 def EnableWriteProtection(self, offset, size):
143 """Enables write protection by specified range."""
144 self._InvokeCommand('--wp-range 0x%06X 0x%06X --wp-enable' % (offset, size))
145 # Try to verify write protection by attempting to disable it.
146 self._InvokeCommand('--wp-disable --wp-range 0 0', ignore_status=True)
147 # Verify the results
148 result = self.GetWriteProtectionStatus()
149 if ((not result.enabled) or (result.offset != offset) or
150 (result.size != size)):
Hung-Te Lind44e1292017-02-15 11:34:26 +0800151 raise IOError('Failed to enabled write protection.')
Hung-Te Linb33e5642012-03-02 16:12:34 +0800152
153 def DisableWriteProtection(self):
154 """Tries to Disable whole write protection range and status."""
155 self._InvokeCommand('--wp-disable --wp-range 0 0')
156 result = self.GetWriteProtectionStatus()
Hung-Te Lind44e1292017-02-15 11:34:26 +0800157 if result.enabled or (result.offset != 0) or (result.size != 0):
158 raise IOError('Failed to disable write protection.')
Hung-Te Linb33e5642012-03-02 16:12:34 +0800159
160
161class FirmwareImage(object):
162 """Provides access to firmware image via FMAP sections."""
Hung-Te Lin56b18402015-01-16 14:52:30 +0800163
Hung-Te Linb33e5642012-03-02 16:12:34 +0800164 def __init__(self, image_source):
Tammo Spalink01e11722012-07-24 10:17:54 -0700165 self._image = image_source
Hung-Te Linb33e5642012-03-02 16:12:34 +0800166 self._fmap = fmap.fmap_decode(self._image)
167 self._areas = dict(
168 (entry['name'], [entry['offset'], entry['size']])
169 for entry in self._fmap['areas'])
170
171 def get_size(self):
172 """Returns the size of associate firmware image."""
173 return len(self._image)
174
175 def has_section(self, name):
176 """Returns if specified section is available in image."""
177 return name in self._areas
178
179 def get_section_area(self, name):
180 """Returns the area (offset, size) information of given section."""
181 if not self.has_section(name):
182 raise ValueError('get_section_area: invalid section: %s' % name)
183 return self._areas[name]
184
185 def get_section(self, name):
186 """Returns the content of specified section."""
187 area = self.get_section_area(name)
188 return self._image[area[0]:(area[0] + area[1])]
189
190 def get_section_offset(self, name):
191 area = self.get_section_area(name)
192 return self._image[area[0]:(area[0] + area[1])]
193
194 def put_section(self, name, value):
195 """Updates content of specified section in image."""
196 area = self.get_section_area(name)
197 if len(value) != area[1]:
Hung-Te Lin56b18402015-01-16 14:52:30 +0800198 raise ValueError('Value size (%d) does not fit into section (%s, %d)' %
Hung-Te Linb33e5642012-03-02 16:12:34 +0800199 (len(value), name, area[1]))
200 self._image = (self._image[0:area[0]] +
201 value +
202 self._image[(area[0] + area[1]):])
203 return True
204
205 def get_fmap_blob(self):
206 """Returns the re-encoded fmap blob from firmware image."""
207 return fmap.fmap_encode(self._fmap)
208
209
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800210class FirmwareContent(object):
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800211 """Wrapper around flashrom for a specific firmware target.
Hung-Te Linb33e5642012-03-02 16:12:34 +0800212
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800213 This class keeps track of all the instances of itself that exist.
214 The goal being that only one instance ever gets created for each
215 target. This mapping of targets to instances is tracked by the
216 _target_cache class data member.
Hung-Te Linb33e5642012-03-02 16:12:34 +0800217 """
Hung-Te Linb33e5642012-03-02 16:12:34 +0800218
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800219 # Cache of target:instance pairs.
220 _target_cache = {}
221
222 @classmethod
Tammo Spalink01e11722012-07-24 10:17:54 -0700223 def Load(cls, target):
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800224 """Create class instance for target, using cached copy if available."""
Tammo Spalink01e11722012-07-24 10:17:54 -0700225 if target in cls._target_cache:
226 return cls._target_cache[target]
227 obj = cls()
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800228 obj.target = target
229 obj.flashrom = Flashrom(target)
Tammo Spalink01e11722012-07-24 10:17:54 -0700230 cls._target_cache[target] = obj
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800231 return obj
232
233 def GetChipId(self):
234 """Caching get of flashrom chip identifier. None if no chip is present."""
235 if not hasattr(self, 'chip_id'):
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800236 info = self.flashrom.GetName()
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800237 self.chip_id = ' '.join([info['vendor'], info['name']]) if info else None
238 return self.chip_id
239
240 def GetFileName(self):
241 """Filename containing firmware data. None if no chip is present."""
242 if self.GetChipId() is None:
243 return None
244 if not hasattr(self, 'filename'):
Hung-Te Lind44e1292017-02-15 11:34:26 +0800245 fileref = tempfile.NamedTemporaryFile(prefix='fw_%s_' % self.target)
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800246 self.flashrom.Read(filename=fileref.name)
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800247 self.fileref = fileref
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800248 self.filename = fileref.name
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800249 return self.filename
Hung-Te Linb33e5642012-03-02 16:12:34 +0800250
Tammo Spalink9a96b8a2012-04-03 11:10:41 +0800251 def Write(self, sections=None):
252 """Call flashrom write for specific sections."""
253 self.flashrom.Write(filename=self.GetFileName(), sections=sections)
254
Hung-Te Lindd708d42014-07-11 17:05:01 +0800255 def GetFirmwareImage(self):
256 """Returns a FirmwareImage instance."""
257 with open(self.GetFileName(), 'rb') as image:
258 return FirmwareImage(image.read())
259
Hung-Te Linb33e5642012-03-02 16:12:34 +0800260
261def LoadEcFirmware():
262 """Returns flashrom data from Embedded Controller chipset."""
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800263 return FirmwareContent.Load(TARGET_EC)
Hung-Te Linb33e5642012-03-02 16:12:34 +0800264
265
Ricky Liangfb3d01b2014-08-27 12:02:20 +0800266def LoadPDFirmware():
267 """Returns flashrom data from Power Delivery chipset."""
268 return FirmwareContent.Load(TARGET_PD)
269
Hung-Te Lin56b18402015-01-16 14:52:30 +0800270
Hung-Te Linb33e5642012-03-02 16:12:34 +0800271def LoadMainFirmware():
272 """Returns flashrom data from main firmware (also known as BIOS)."""
Tammo Spalinkcc87d832012-03-28 09:57:40 +0800273 return FirmwareContent.Load(TARGET_MAIN)