Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(491)

Unified Diff: x86-generic/flashrom_util.py

Issue 3328008: Make SAFT use layout retrieved from the BIOS image. (Closed) Base URL: http://git.chromium.org/git/firmware.git
Patch Set: Cosmetic changes before committing. Created 10 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « saft/test_kernel_handler.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: x86-generic/flashrom_util.py
diff --git a/x86-generic/flashrom_util.py b/x86-generic/flashrom_util.py
index 8a44ed61e2796a45258ed7e17a5aaee400c55997..5ff95a5e405160fe33f6f8f432ee34abc9074597 100644
--- a/x86-generic/flashrom_util.py
+++ b/x86-generic/flashrom_util.py
@@ -24,95 +24,133 @@ For more information, see help(flashrom_util.flashrom_util).
import os
import re
+import stat
import subprocess
import sys
import tempfile
import types
+import chromeos_interface
-# simple layout description language compiler
-def compile_layout(desc, size):
- """ compile_layout(desc, size) -> layout
-
- Compiles a flashrom layout by simple description language.
- Returns the result as a map. Empty map for any error.
-
- syntax: <desc> ::= <partitions>
- <partitions> ::= <partition>
- | <partitions> '|' <partition>
- <partition> ::= <spare_section>
- | <partition> ',' <section>
- | <section> ',' <partition>
- <section> ::= <name> '=' <size>
- <spare_section> ::= '*'
- | <name>
- | <name> '=' '*'
-
- * Example: 'ro|rw', 'ro=0x1000,*|*,rw=0x1000'
- * Each partition share same space from total size of flashrom.
- * Sections are fix sized, or "spare" which consumes all remaining
- space from a partition.
- * You can use any non-zero decimal or heximal (0xXXXX) in <size>.
- (size as zero is reserved now)
- * You can use '*' as <name> for "unamed" items which will be ignored in
- final layout output.
- * You can use "<name>=*" or simply "<name>" (including '*', the
- 'unamed section') to define spare section.
- * There must be always one (no more, no less) spare section in
- each partition.
- """
- # create an empty layout first
- layout = {}
- err_ret = {}
-
- # prepare: remove all spaces (literal from string.whitespace)
- desc = ''.join([c for c in desc if c not in '\t\n\x0b\x0c\r '])
- # find equally-sized partitions
- parts = desc.split('|')
- block_size = size / len(parts)
- offset = 0
-
- for part in parts:
- sections = part.split(',')
- sizes = []
- names = []
- spares = 0
-
- for section in sections:
- # skip empty section to allow final ','
- if section == '':
+class TestError(Exception):
+ pass
+
+
+class LayoutScraper(object):
+ '''Object of this class is used to retrieve layout from a BIOS file.'''
+
+ # The default conversion table for mosys.
+ DEFAULT_CHROMEOS_FMAP_CONVERSION = {
+ "Boot Stub": "FV_BSTUB",
+ "GBB Area": "FV_GBB",
+ "Recovery Firmware": "FVDEV",
+ "RO VPD": "RO_VPD",
+ "Firmware A Key": "VBOOTA",
+ "Firmware A Data": "FVMAIN",
+ "Firmware B Key": "VBOOTB",
+ "Firmware B Data": "FVMAINB",
+ "Log Volume": "FV_LOG",
+ }
+
+ def __init__(self, os_if):
+ self.image = None
+ self.os_if = os_if
+
+ def _get_text_layout(self, file_name):
+ '''Retrieve text layout from a firmware image file.
+
+ This function uses the 'mosys' utility to scan the firmware image and
+ retrieve the section layout information.
+
+ The layout is reported as a set of lines with multiple
+ "<name>"="value" pairs, all this output is passed to the caller.
+ '''
+
+ mosys_cmd = 'mosys -k eeprom map %s' % file_name
+ return self.os_if.run_shell_command_get_output(mosys_cmd)
+
+ def _line_to_dictionary(self, line):
+ '''Convert a text layout line into a dictionary.
+
+ Get a string consisting of single space separated "<name>"="value>"
+ pairs and convert it into a dictionary where keys are the <name>
+ fields, and values are the corresponding <value> fields.
+
+ Return the dictionary to the caller.
+ '''
+
+ rv = {}
+
+ items = line.replace('" ', '"^').split('^')
+ for item in items:
+ pieces = item.split('=')
+ if len(pieces) != 2:
continue
- # format name=v or name ?
- if section.find('=') >= 0:
- k, v = section.split('=')
- if v == '*':
- v = 0 # spare section
- else:
- v = int(v, 0)
- if v == 0:
- raise TestError('Using size as 0 is prohibited now.')
- else:
- k, v = (section, 0) # spare, should appear for only one.
- if v == 0:
- spares = spares + 1
- names.append(k)
- sizes.append(v)
-
- if spares != 1:
- # each partition should have exactly one spare field
- return err_ret
-
- spare_size = block_size - sum(sizes)
- sizes[sizes.index(0)] = spare_size
- # fill sections
- for i in range(len(names)):
- # ignore unamed sections
- if names[i] != '*':
- layout[names[i]] = (offset, offset + sizes[i] - 1)
- offset = offset + sizes[i]
-
- return layout
+ rv[pieces[0]] = pieces[1].strip('"')
+ return rv
+
+ def check_layout(self, layout, file_size):
+ '''Verify the layout to be consistent.
+
+ The layout is consistent if there is no overlapping sections and the
+ section boundaries do not exceed the file size.
+
+ Inputs:
+ layout: a dictionary keyed by a string (the section name) with
+ values being two integers tuples, the first and the last
+ bites' offset in the file.
+ file_size: and integer, the size of the file the layout describes
+ the sections in.
+
+ Raises:
+ TestError in case the layout is not consistent.
+ '''
+
+ # Generate a list of section range tuples.
+ ost = sorted([layout[section] for section in layout])
+ base = 0
+ for section_base, section_end in ost:
+ if section_base < base or section_end < section_base:
+ raise TestError('bad section at 0x%x..0x%x' % (
+ section_base, section_end))
+ base = section_end
+ if base > file_size:
+ raise TestError('Section end 0x%x exceeds file size %x' % (
+ base, file_size))
+
+ def get_layout(self, file_name):
+ '''Generate layout for a firmware file.
+
+ First retrieve the text layout as reported by 'mosys' and then convert
+ it into a dictionary, replacing section names reported by mosys into
+ matching names from DEFAULT_CHROMEOS_FMAP_CONVERSION dictionary above,
+ using the names as keys in the layout dictionary. The elements of the
+ layout dictionary are the offsets of the first ans last bytes of the
+ section in the firmware file.
+
+ Then verify the generated layout's consistency and return it to the
+ caller.
+ '''
+
+ layout_data = {} # keyed by the section name, elements - tuples of
+ # (<section start addr>, <section end addr>)
+
+ for line in self._get_text_layout(file_name):
+ d = self._line_to_dictionary(line)
+ try:
+ name = self.DEFAULT_CHROMEOS_FMAP_CONVERSION[d['area_name']]
+ except KeyError:
+ continue # This line does not contain an area of interest.
+
+ if name in layout_data:
+ raise TestError('%s duplicated in the layout' % area_name)
+
+ offset = int(d['area_offset'], 0)
+ size = int(d['area_size'], 0)
+ layout_data[name] = (offset, offset + size - 1)
+ self.check_layout(layout_data, os.stat(file_name)[stat.ST_SIZE])
+ return layout_data
# flashrom utility wrapper
class flashrom_util(object):
@@ -129,110 +167,61 @@ class flashrom_util(object):
To perform a read, you need to:
1. Prepare a flashrom_util object
ex: flashrom = flashrom_util.flashrom_util()
- 2. Decide target (BIOS/EC)
- ex: flashrom.select_bios_flashrom()
- 3. Perform read operation
+ 2. Perform read operation
ex: image = flashrom.read_whole()
- To perform a (partial) write, you need to:
- 1. Select target (BIOS/EC)
- ex: flashrom.select_ec_flashrom()
- 2. Create or load a layout map (see explain of layout below)
- ex: layout_map = { 'all': (0, rom_size - 1) }
- ex: layout_map = { 'ro': (0, 0xFFF), 'rw': (0x1000, rom_size-1) }
- You can also use built-in layout like detect_chromeos_bios_layout(),
- detect_chromeos_layout(), or detect_layout() to build the layout maps.
- 3. Prepare a full base image
- ex: image = flashrom.read_whole()
- ex: image = chr(0xFF) * rom_size
- 4. (optional) Modify data in base image
- ex: new_image = flashrom.put_section(image, layout_map, 'all', mydata)
- 5. Perform write operation
- ex: flashrom.write_partial(new_image, layout_map, ('all',))
+ When the contents of the flashrom is read off the target, it's map
+ gets created automatically (read from the flashrom image using
+ 'mosys'). If the user wants this object to operate on some other file,
+ he could either have the map for the file created explicitly by
+ invoking flashrom.set_bios_layout(filename), or supply his own map
+ (which is a dictionary where keys are section names, and values are
+ tuples of integers, base address of the section and the last address
+ of the section).
+
+ By default this object operates on the map retrieved from the image and
+ stored locally, this map can be overwritten by an explicitly passed user
+ map.
- P.S: you can also create the new_image in your own way, for example:
- rom_size = flashrom_util.get_size()
- erase_image = chr(0xFF) * rom_size
- flashrom.write_partial(erase_image, layout_map, ('all',))
+ To perform a (partial) write:
- The layout is a dictionary of { 'name': (address_begin, addres_end) }.
- Note that address_end IS included in the range.
- See help(detect_layout) for easier way to generate layout maps.
+ 1. Prepare a buffer storing an image to be written into the flashrom.
+ 2. Have the map generated automatically or prepare your own, for instance:
+ ex: layout_map_all = { 'all': (0, rom_size - 1) }
+ ex: layout_map = { 'ro': (0, 0xFFF), 'rw': (0x1000, rom_size-1) }
+ 4. Perform write operation
+
+ ex using default map:
+ flashrom.write_partial(new_image, (<section_name>, ...))
+ ex using explicitly provided map:
+ flashrom.write_partial(new_image, layout_map_all, ('all',))
Attributes:
- tool_path: file path to the tool 'flashrom'
- cmd_prefix: prefix of every shell cmd, ex: "PATH=.:$PATH;export PATH;"
- tmp_root: a folder name for mkstemp (for temp of layout and images)
verbose: print debug and helpful messages
keep_temp_files: boolean flag to control cleaning of temporary files
- target_map: map of what commands should be invoked to switch targets.
- if you don't need any commands, use empty dict {}.
- if you want default detection, use None (default param).
"""
- # default target selection commands, by machine architecture
- # syntax: { 'arch_regex': exec_script, ... }
- default_arch_target_map = {
- '^x86|^i\d86': {
- # The magic numbers here are register indexes and values that apply
- # to all current known x86 based ChromeOS devices.
- # Detail information is defined in section #"10.1.50 GCS-General
- # Control and Status Register" of document "Intel NM10 Express
- # Chipsets".
- "bios": 'iotools mmio_write32 0xfed1f410 ' +
- '`iotools mmio_read32 0xfed1f410 |head -c 6`0460',
- "ec": 'iotools mmio_write32 0xfed1f410 ' +
- '`iotools mmio_read32 0xfed1f410 |head -c 6`0c60',
- },
- }
-
- default_chromeos_layout_desc = {
- "bios": """
- FV_LOG = 0x20000,
- NV_COMMON_STORE = 0x10000,
- VBOOTA = 0x02000,
- FVMAIN = 0xB0000,
- VBOOTB = 0x02000,
- FVMAINB = 0xB0000,
- NVSTORAGE = 0x10000,
- FV_RW_RESERVED = *,
- |
- FV_RO_RESERVED = *,
- FVDEV = 0xB0000,
- FV_GBB = 0x20000,
- FV_BSTUB = 0x40000,
- """,
- "ec": """
- EC_RO
- |
- EC_RW
- """,
- }
-
- def __init__(self,
- tool_path='/usr/sbin/flashrom',
- cmd_prefix='',
- tmp_root=None,
- verbose=False,
- keep_temp_files=False,
- target_map=None):
+ def __init__(self, verbose=False, keep_temp_files=False):
""" constructor of flashrom_util. help(flashrom_util) for more info """
- self.tool_path = tool_path
- self.cmd_prefix = cmd_prefix
- self.tmp_root = tmp_root
self.verbose = verbose
self.keep_temp_files = keep_temp_files
- self.target_map = target_map
- # detect bbs map if target_map is None.
- # NOTE when target_map == {}, that means "do not execute commands",
- # different to default value.
- if isinstance(target_map, types.NoneType):
- # generate default target map
- self.target_map = self.detect_target_map()
+ self.bios_layout = {}
+ self.os_if = chromeos_interface.ChromeOSInterface(True)
+ self.os_if.init(tempfile.gettempdir())
+ self._enable_bios_access()
+
+ def _enable_bios_access(self):
+ if not self.os_if.target_hosted():
+ return
+ value = int(self.os_if.run_shell_command_get_output(
+ 'iotools mmio_read32 0xfed1f410')[0], 0)
+ value = (value & 0xffff0000) + 0x460
+ self.os_if.run_shell_command(
+ 'iotools mmio_write32 0xfed1f410 0x%x' % value)
def get_temp_filename(self, prefix):
''' (internal) Returns name of a temporary file in self.tmp_root '''
- (fd, name) = tempfile.mkstemp(prefix=prefix, dir=self.tmp_root)
+ (fd, name) = tempfile.mkstemp(prefix=prefix)
os.close(fd)
return name
@@ -255,24 +244,24 @@ class flashrom_util(object):
open(tmpfn, 'wb').write('\n'.join(layout_text) + '\n')
return tmpfn
- def get_section(self, base_image, layout_map, section_name):
+ def get_section(self, base_image, section_name):
'''
Retrieves a section of data based on section_name in layout_map.
Raises error if unknown section or invalid layout_map.
'''
- pos = layout_map[section_name]
+ pos = self.bios_layout[section_name]
if pos[0] >= pos[1] or pos[1] >= len(base_image):
raise TestError('INTERNAL ERROR: invalid layout map: %s.' %
section_name)
return base_image[pos[0] : pos[1] + 1]
- def put_section(self, base_image, layout_map, section_name, data):
+ def put_section(self, base_image, section_name, data):
'''
- Updates a section of data based on section_name in layout_map.
- Raises error if unknown section or invalid layout_map.
+ Updates a section of data based on section_name in bios_layout.
+ Raises error if unknown section.
Returns the full updated image data.
'''
- pos = layout_map[section_name]
+ pos = self.bios_layout[section_name]
if pos[0] >= pos[1] or pos[1] >= len(base_image):
raise TestError('INTERNAL ERROR: invalid layout map.')
if len(data) != pos[1] - pos[0] + 1:
@@ -287,52 +276,11 @@ class flashrom_util(object):
image = self.read_whole()
return len(image)
- def detect_target_map(self):
- """
- Detects the target selection map.
- Use machine architecture in current implementation.
- """
- arch = utils.get_arch()
- for regex, target_map in self.default_arch_target_map.items():
- if re.match(regex, arch):
- return target_map
- raise TestError('INTERNAL ERROR: unknown architecture, need target_map')
-
- def detect_layout(self, layout_desciption, size=None):
- """
- Detects and builds layout according to current flash ROM size
- and a simple layout description language.
- If parameter 'size' is omitted, self.get_size() will be called.
-
- See help(flashrom_util.compile_layout) for the syntax of description.
-
- Returns the layout map (empty if any error).
- """
- if not size:
- size = self.get_size()
- return compile_layout(layout_desciption, size)
-
- def detect_chromeos_layout(self, target, size=None):
- """
- Detects and builds ChromeOS firmware layout according to current flash
- ROM size. If parameter 'size' is None, self.get_size() will be called.
-
- Currently supported targets are: 'bios' or 'ec'.
-
- Returns the layout map (empty if any error).
- """
- if target not in self.default_chromeos_layout_desc:
- raise TestError('INTERNAL ERROR: unknown layout target: %s' % test)
- chromeos_target = self.default_chromeos_layout_desc[target]
- return self.detect_layout(chromeos_target, size)
-
- def detect_chromeos_bios_layout(self, size=None):
- """ Detects standard ChromeOS BIOS layout """
- return self.detect_chromeos_layout('bios', size)
-
- def detect_chromeos_ec_layout(self, size=None):
- """ Detects standard ChromeOS Embedded Controller layout """
- return self.detect_chromeos_layout('ec', size)
+ def set_bios_layout(self, file_name):
+ """get layout read from the BIOS """
+
+ scraper = LayoutScraper(self.os_if)
+ self.bios_layout = scraper.get_layout(file_name)
def read_whole(self):
'''
@@ -340,114 +288,45 @@ class flashrom_util(object):
Returns the data read from flash ROM, or empty string for other error.
'''
tmpfn = self.get_temp_filename('rd_')
- cmd = '%s"%s" -r "%s"' % (self.cmd_prefix, self.tool_path, tmpfn)
+ cmd = 'flashrom -r "%s"' % (tmpfn)
if self.verbose:
print 'flashrom_util.read_whole(): ', cmd
- result = ''
- if utils.system(cmd, ignore_status=True) == 0: # failure for non-zero
- try:
- result = open(tmpfn, 'rb').read()
- except IOError:
- result = ''
+ self.os_if.run_shell_command(cmd)
+ result = open(tmpfn, 'rb').read()
+ self.set_bios_layout(tmpfn)
# clean temporary resources
self.remove_temp_file(tmpfn)
return result
- def write_partial(self, base_image, layout_map, write_list):
+ def write_partial(self, base_image, write_list, write_layout_map=None):
'''
Writes data in sections of write_list to flash ROM.
- Returns True on success, otherwise False.
+ An exception is raised if write operation fails.
'''
+
+ if write_layout_map:
+ layout_map = write_layout_map
+ else:
+ layout_map = self.bios_layout
+
tmpfn = self.get_temp_filename('wr_')
open(tmpfn, 'wb').write(base_image)
layout_fn = self.create_layout_file(layout_map)
- cmd = '%s"%s" -l "%s" -i %s -w "%s"' % (
- self.cmd_prefix, self.tool_path,
+ cmd = 'flashrom -l "%s" -i %s -w "%s"' % (
layout_fn, ' -i '.join(write_list), tmpfn)
if self.verbose:
print 'flashrom.write_partial(): ', cmd
- result = False
- if utils.system(cmd, ignore_status=True) == 0: # failure for non-zero
- result = True
+ self.os_if.run_shell_command(cmd)
# clean temporary resources
self.remove_temp_file(tmpfn)
self.remove_temp_file(layout_fn)
- return result
- def select_target(self, target):
- '''
- Selects (usually by setting BBS register) a target defined in target_map
- and then directs all further firmware access to certain region.
- '''
- if target not in self.target_map:
- return True
- if self.verbose:
- print 'flashrom.select_target("%s"): %s' % (target,
- self.target_map[target])
- if utils.system(self.cmd_prefix + self.target_map[target],
- ignore_status=True) == 0:
- return True
- return False
-
- def select_bios_flashrom(self):
- ''' Directs all further accesses to BIOS flash ROM. '''
- return self.select_target('bios')
-
- def select_ec_flashrom(self):
- ''' Directs all further accesses to Embedded Controller flash ROM. '''
- return self.select_target('ec')
-
-
-# ---------------------------------------------------------------------------
-# The flashrom_util supports both running inside and outside 'autotest'
-# framework, so we need to provide some mocks and dynamically load
-# autotest components here.
-
-
-class mock_TestError(object):
- """ a mock for error.TestError """
- def __init__(self, msg):
- print msg
- sys.exit(1)
-
-
-class mock_utils(object):
- """ a mock for autotest_li.client.bin.utils """
- def get_arch(self):
- arch = os.popen('uname -m').read().rstrip()
- arch = re.sub(r"i\d86", r"i386", arch, 1)
- return arch
-
- def system(self, cmd, ignore_status=False):
- p = subprocess.Popen(cmd, shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- p.wait()
- if p.returncode:
- print p.stdout.read()
- if not ignore_status:
- raise TestError("failed to execute: %s\nError messages: %s" % (
- cmd, p.stderr.read()))
- return p.returncode
-
-
-# import autotest or mock utilities
-try:
- # print 'using autotest'
- from autotest_lib.client.bin import test, utils
- from autotest_lib.client.common_lib.error import TestError
-except ImportError:
- # print 'using mocks'
- utils = mock_utils()
- TestError = mock_TestError
-
-
-# main stub
-if __name__ == "__main__":
- # TODO(hungte) provide unit tests or command line usage
- pass
+ def write_whole(self, base_image):
+ '''Write the whole base image. '''
+ layout_map = { 'all': (0, len(base_image) - 1) }
+ self.write_partial(base_image, ('all',), layout_map)
« no previous file with comments | « saft/test_kernel_handler.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698