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) |