A complete rewrite of the layout file parser: - safe (unlike before) - support for comments - supports including/sourcing of other layout files (relative to the including file as well as via absolute paths) up to predefined nesting level - checks for duplicate region names - can handle region names and layout file names with spaces etc correctly - way better diagnostics - no more character arrays in struct romentry_t but pointers only
To help with migrating legacy layout files add a script for converting them to format 2.
Also, document the whole layout handling including the new features a bit better and refine wording regarding files, images, layouts and regions as discussed.
Signed-off-by: Stefan Tauner stefan.tauner@student.tuwien.ac.at --- flashrom.8.tmpl | 130 ++++++++++------ layout.c | 332 +++++++++++++++++++++++++++++++++------- util/convert_layout_v1_to_v2.sh | 79 ++++++++++ 3 files changed, 437 insertions(+), 104 deletions(-) create mode 100755 util/convert_layout_v1_to_v2.sh
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index b507a97..06282f6 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -3,10 +3,10 @@ flashrom - detect, read, write, verify and erase flash chips .SH SYNOPSIS .B flashrom \fR[\fB-h\fR|\fB-R\fR|\fB-L\fR|\fB-z\fR|\ -\fB-p\fR <programmername>[:<parameters>] - [\fB-E\fR|\fB-r\fR <file>|\fB-w\fR <file>|\fB-v\fR <file>] \ +\fB-p\fR <programmername>[:<parameter>[,<parameter>]...]] + [\fB-E\fR|\fB-r\fR <imagefile>|\fB-w\fR <imagefile>|\fB-v\fR <imagefile>] \ [\fB-c\fR <chipname>] - [\fB-l\fR <file> [\fB-i\fR <image>]] [\fB-n\fR] [\fB-f\fR]] + [\fB-l\fR <layoutfile> [\fB-i\fR <regionname>[:<regionfile>]]...] [\fB-n\fR] [\fB-f\fR]] [\fB-V\fR[\fBV\fR[\fBV\fR]]] [\fB-o\fR <logfile>] .SH DESCRIPTION .B flashrom @@ -38,14 +38,14 @@ before you try to write a new image. All operations involving any chip access (p .B -p/--programmer option to be used (please see below). .TP -.B "-r, --read <file>" +.B "-r, --read <imagefile>" Read flash ROM contents and save them into the given -.BR <file> . +.BR <imagefile> . If the file already exists, it will be overwritten. .TP -.B "-w, --write <file>" +.B "-w, --write <imagefile>" Write -.B <file> +.B <imagefile> into flash ROM. This will first automatically .B erase the chip, then write to it. @@ -65,14 +65,14 @@ recommended, you should only use it if you know what you are doing and if you feel that the time for verification takes too long. .sp Typical usage is: -.B "flashrom -p prog -n -w <file>" +.B "flashrom -p prog -n -w <imagefile>" .sp This option is only useful in combination with .BR --write . .TP -.B "-v, --verify <file>" +.B "-v, --verify <imagefile>" Verify the flash ROM contents against the given -.BR <file> . +.BR <imagefile> . .TP .B "-E, --erase" Erase the flash ROM chip. @@ -102,46 +102,6 @@ size for the flash bus. .sp * Force write even if write is known bad. .TP -.B "-l, --layout <file>" -Read ROM layout from -.BR <file> . -.sp -flashrom supports ROM layouts. This allows you to flash certain parts of -the flash chip only. A ROM layout file contains multiple lines with the -following syntax: -.sp -.B " startaddr:endaddr imagename" -.sp -.BR "startaddr " "and " "endaddr " -are hexadecimal addresses within the ROM file and do not refer to any -physical address. Please note that using a 0x prefix for those hexadecimal -numbers is not necessary, but you can't specify decimal/octal numbers. -.BR "imagename " "is an arbitrary name for the region/image from" -.BR " startaddr " "to " "endaddr " "(both addresses included)." -.sp -Example: -.sp - 00000000:00008fff gfxrom - 00009000:0003ffff normal - 00040000:0007ffff fallback -.sp -If you only want to update the image named -.BR "normal " "in a ROM based on the layout above, run" -.sp -.B " flashrom -p prog --layout rom.layout --image normal -w some.rom" -.sp -To update only the images named -.BR "normal " "and " "fallback" ", run:" -.sp -.B " flashrom -p prog -l rom.layout -i normal -i fallback -w some.rom" -.sp -Overlapping sections are not supported. -.TP -.B "-i, --image <imagename>" -Only flash region/image -.B <imagename> -from flash layout. -.TP .B "-L, --list-supported" List the flash chips, chipsets, mainboards, and external programmers (including PCI, USB, parallel port, and serial port based devices) @@ -226,6 +186,76 @@ Some programmers have optional or mandatory parameters which are described in detail in the .B PROGRAMMER SPECIFIC INFO section. Support for some programmers can be disabled at compile time. +.TP +flashrom supports reading/writing/erasing/verifying flash chips in whole (default) or in part. To access only \ +parts of a chip one has to use layout files and respective arguments described below. +.TP +.B "-l, --layout <layoutfile>" +Read layout entries from +.BR <layoutfile> . +.sp +A layout file can contain comments which are +started by a pound sign (#) and causes all following characters on the same line to be ignored with one +exception: Every layout file +.B must +start with a comment in the following form to define the version of the file. Example for the current version 2: +.sp +.B " # flashrom layout v2" +.sp +Every other line may contain either a command to include another layout file, a layout entry or white space +(and an optional comment at the end). +.sp +To include another file you can use the +.sp +.B " source <path>" +.sp +syntax where +.B path +specifies the absolute or relative path to the file to be included. Relative paths are interpreted to be +relative to the file containing the include command. If the path contains spaces it has to be written in double +quotes, or else only the part before the first space will be recognized. +.sp +Each layout entry describes an address region of the flash chip and gives it a name (hereinafter referred to as +a region). One entry per line is allowed with the following syntax: +.sp +.B " startaddr:endaddr regionname" +.sp +.BR "startaddr " "and " "endaddr " +are addresses within the ROM image representing the flash ROM contents. They are interpreted in the 'usual' form +i.e.\ a leading 0 means octal, leading 0x or 0X means hexadecimal, everything else is just decimal. +.BR "regionname " "is the name for the region from " "startaddr " "to " "endaddr " "(both addresses included)." +If the name contains spaces it has to be written in double quotes, or else only the part before the first space +will be used. +.sp +Example content of file rom.layout: +.sp + # flashrom layout v2 + source /home/flashrom/include.layout + 0x00000000:0x00008fff "gfx rom" + 0x00009000:0x0003ffff normal + 0x00040000:0x0007ffff fallback +.sp +.TP +.B "-i, --include <name>[:<regionfile>]" +Work on the flash region +.B name +instead of the full address space if a layout file is given and parsed correctly. +Multiple such include parameters can be used to work on the union of different regions. +.sp +The optional +.B regionfile +parameter specifies the name of a file that is used to map the contents of that very region only. +The optional file parameter causes the contents of this region to be replaced by the contents of the file +specified here. +.sp +If you only want to update the regions named +.BR "normal " "and " "gfx rom " "in a ROM based on the layout mentioned above, run" +.sp +.B " flashrom -p prog -l rom.layout -i normal -i ""gfx rom"" -w some.rom" +.sp +Overlapping regions are resolved in an implementation-dependent manner (or may even yield an error) - do +.BR "not " "rely on it." +.sp .B "flashrom -h" lists all supported programmers. .TP diff --git a/layout.c b/layout.c index 7e2e904..6150e7e 100644 --- a/layout.c +++ b/layout.c @@ -22,17 +22,23 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <ctype.h> +#include <errno.h> #include <limits.h> #include "flash.h" #include "programmer.h"
#define MAX_ROMLAYOUT 32 +#define MAX_ENTRY_LEN 1024 +#define WHITESPACE_CHARS " \t" +#define INCLUDE_INSTR "source" +#define MAX_NESTING_LVL 5
typedef struct { chipoff_t start; chipoff_t end; - unsigned int included; - char name[256]; + bool included; + char *name; char *file; } romentry_t;
@@ -45,58 +51,289 @@ static int num_rom_entries = 0; /* the number of successfully parsed rom_entries static char *include_args[MAX_ROMLAYOUT]; static int num_include_args = 0; /* the number of valid include_args. */
-#ifndef __LIBPAYLOAD__ -int read_romlayout(char *name) +/* returns the index of the entry (or a negative value if it is not found) */ +static int find_romentry(char *name) { - FILE *romlayout; - char tempstr[256]; int i; + msg_gspew("Looking for region "%s"... ", name); + for (i = 0; i < num_rom_entries; i++) { + if (strcmp(rom_entries[i].name, name) == 0) { + msg_gspew("found.\n"); + return i; + } + } + msg_gspew("not found.\n"); + return -1; +} + +/* FIXME: While libpayload has no file I/O itself, code using libflashrom could still provide layout information + * obtained by other means like user input or by fetching it from somewhere else. Therefore the parsing code + * should be separated from the file reading code eventually. */ +#ifndef __LIBPAYLOAD__ +/** Parse one line in a layout file. + * @param file_name The name of the file this line originates from. + * @param linecnt Line number the input string originates from. + * @param entry If not NULL fill it with the parsed data, else just detect errors and print diagnostics. + * @return -1 on error, + * 0 if the line could be parsed into a layout entry succesfully, + * 1 if a file was successfully sourced. + */ +static int parse_entry(char *file_name, unsigned int linecnt, char *buf, romentry_t *entry) +{ + msg_gdbg2("String to parse: "%s".\n", buf); + + /* Skip all white space in the beginning. */ + char *tmp_str = buf + strspn(buf, WHITESPACE_CHARS); + char *endptr; + + /* Check for include command. */ + if (strncmp(tmp_str, INCLUDE_INSTR, strlen(INCLUDE_INSTR)) == 0) { + tmp_str += strlen(INCLUDE_INSTR); + tmp_str += strspn(tmp_str, WHITESPACE_CHARS); + if (unquote_string(&tmp_str, NULL, WHITESPACE_CHARS) != 0) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Could not find file name in "%s".\n", + file_name, linecnt, buf); + return -1; + } + msg_gspew("Source command found with filename "%s".\n", tmp_str); + + static unsigned int nesting_lvl = 0; + if (nesting_lvl >= MAX_NESTING_LVL) { + msg_gerr("Error: Nesting level exeeded limit of %u.\n", MAX_NESTING_LVL); + msg_gerr("Unable to import "%s" in layout file "%s".\n", tmp_str, file_name); + return -1; + } + + nesting_lvl++; + int ret; + /* If a relative path is given, append it to the dirname of the current file. */ + if (*tmp_str != '/') { + /* We need space for: dirname of file_name, '/' , the file name in tmp_strand and '\0'. + * Since the dirname of file_name is shorter than file_name this is more than enough: */ + char *path = malloc(strlen(file_name) + strlen(tmp_str) + 2); + if (path == NULL) { + msg_gerr("Out of memory!\n"); + return -1; + } + strcpy(path, file_name); + + /* A less insane but incomplete dirname implementation... */ + endptr = strrchr(path, '/'); + if (endptr != NULL) { + endptr[0] = '/'; + endptr[1] = '\0'; + } else { + /* This is safe because the original file name was at least one char. */ + path[0] = '.'; + path[1] = '/'; + path[2] = '\0'; + } + strcat(path, tmp_str); + ret = read_romlayout(path); + free(path); + } else + ret = read_romlayout(tmp_str); + nesting_lvl--; + return ret >= 0 ? 1 : -1; /* Only return values < 0 are errors. */ + }
- romlayout = fopen(name, "r"); + /* Parse start address. */ + errno = 0; + long long tmp_addr = strtoll(tmp_str, &endptr, 0); + if (errno != 0 || endptr == tmp_str) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Could not convert start address in "%s".\n", file_name, linecnt, buf); + return -1; + } + if (tmp_addr < 0 || tmp_addr > FL_MAX_CHIPADDR) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Start address (%s0x%llx) in "%s" is beyond the supported range (max 0x%" + PRIxCHIPADDR ").\n", file_name, linecnt, (tmp_addr < 0) ? "-" : "", + llabs(tmp_addr), buf, FL_MAX_CHIPADDR); + return -1; + } + chipoff_t start = (chipoff_t)tmp_addr;
- if (!romlayout) { - msg_gerr("ERROR: Could not open layout file (%s).\n", - name); + tmp_str = endptr + strspn(endptr, WHITESPACE_CHARS); + if (*tmp_str != ':') { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Address separator does not follow start address in "%s".\n", + file_name, linecnt, buf); return -1; } + tmp_str++; + + /* Parse end address. */ + errno = 0; + tmp_addr = strtoll(tmp_str, &endptr, 0); + if (errno != 0 || endptr == tmp_str) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Could not convert end address in "%s".\n", file_name, linecnt, buf); + return -1; + } + if (tmp_addr < 0 || tmp_addr > FL_MAX_CHIPADDR) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "End address (%s0x%llx) in "%s" is beyond the supported range (max 0x%" + PRIxCHIPADDR ").\n", file_name, linecnt, (tmp_addr < 0) ? "-" : "", + llabs(tmp_addr), buf, FL_MAX_CHIPADDR); + return -1; + } + chipoff_t end = (chipoff_t)tmp_addr;
- while (!feof(romlayout)) { - char *tstr1, *tstr2; + size_t skip = strspn(endptr, WHITESPACE_CHARS); + if (skip == 0) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "End address is not followed by white space in "%s"\n", file_name, linecnt, buf); + return -1; + }
- if (num_rom_entries >= MAX_ROMLAYOUT) { - msg_gerr("Maximum number of entries (%i) in layout file reached.\n", MAX_ROMLAYOUT); - return 1; + /* Parse region name. */ + tmp_str = endptr + skip; + /* The region name is either enclosed by quotes or ends with the first whitespace. */ + if (unquote_string(&tmp_str, &endptr, WHITESPACE_CHARS) != 0) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Could not find region name in "%s".\n", file_name, linecnt, buf); + return -1; + } + + msg_gdbg2("Parsed entry: 0x%" PRIxCHIPADDR " - 0x%" PRIxCHIPADDR " named "%s"\n", + start, end, tmp_str); + + if (start >= end) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Length of region "%s" is not positive.\n", file_name, linecnt, tmp_str); + return -1; + } + + if (find_romentry(tmp_str) >= 0) { + msg_gerr("Error parsing version 2 layout entry in file "%s" at line %d:\n" + "Region name "%s" used multiple times.\n", file_name, linecnt, tmp_str); + return -1; + } + + endptr += strspn(endptr, WHITESPACE_CHARS); + if (strlen(endptr) != 0) + msg_gwarn("Warning parsing version 2 layout entry in file "%s" at line %d:\n" + "Region name "%s" is not followed by white space only.\n", + file_name, linecnt, tmp_str); + + if (entry != NULL) { + entry->name = strdup(tmp_str); + if (entry->name == NULL) { + msg_gerr("Out of memory!\n"); + return -1; } - if (2 != fscanf(romlayout, "%s %s\n", tempstr, rom_entries[num_rom_entries].name)) - continue; -#if 0 - // fscanf does not like arbitrary comments like that :( later - if (tempstr[0] == '#') { - continue; + + entry->start = start; + entry->end = end; + entry->included = 0; + entry->file = NULL; + } + return 0; +} + +/* Scan the first line for the determinant version comment and parse it, or assume it is version 1. */ +static int detect_layout_version(FILE *romlayout) +{ + int c; + do { /* Skip white space */ + c = fgetc(romlayout); + if (c == EOF) + return -1; + } while (isblank(c)); + ungetc(c, romlayout); + + const char* vcomment = "# flashrom layout v"; + char buf[strlen(vcomment) + 1]; /* comment + \0 */ + if (fgets(buf, sizeof(buf), romlayout) == NULL) + return -1; + if (strcmp(vcomment, buf) != 0) + return 1; + int version; + if (fscanf(romlayout, "%d", &version) != 1) + return -1; + if (version < 2) { + msg_gwarn("Warning: Layout file declares itself to be version %d, but self declaration has\n" + "only been possible since version 2. Continuing anyway.\n", version); + } + return version; +} + +int read_romlayout(char *name) +{ + FILE *romlayout = fopen(name, "r"); + if (romlayout == NULL) { + msg_gerr("ERROR: Could not open layout file "%s".\n", name); + return -1; + } + + const int version = detect_layout_version(romlayout); + if (version < 0) { + msg_gerr("Could not determine version of layout file "%s".\n", name); + fclose(romlayout); + return 1; + } + if (version != 2) { + msg_gerr("Layout file version %d is not supported in this version of flashrom.\n", version); + fclose(romlayout); + return 1; + } + rewind(romlayout); + + msg_gdbg("Parsing layout file "%s" according to version %d.\n", name, version); + unsigned int linecnt = 0; + while (!feof(romlayout)) { + char buf[MAX_ENTRY_LEN]; + char *curchar = buf; + + while (true) { + /* Make sure that we ignore various newline sequences by checking for \r too. + * NB: This might introduce empty lines. */ + char c = fgetc(romlayout); + if (c == '#') { + do { /* Skip characters in comments */ + c = fgetc(romlayout); + } while (c != EOF && c != '\n' && c != '\r'); + linecnt++; + continue; + } + if (c == EOF || c == '\n' || c == '\r') { + *curchar = '\0'; + linecnt++; + break; + } + if (curchar == &buf[MAX_ENTRY_LEN - 1]) { + msg_gerr("Line %d of layout file "%s" is longer than the allowed %d chars.\n", + linecnt, name, MAX_ENTRY_LEN); + fclose(romlayout); + return 1; + } + *curchar = c; + curchar++; } -#endif - tstr1 = strtok(tempstr, ":"); - tstr2 = strtok(NULL, ":"); - if (!tstr1 || !tstr2) { - msg_gerr("Error parsing layout file. Offending string: "%s"\n", tempstr); + msg_gspew("Parsing line %d of "%s".\n", linecnt, name); + + /* Skip all whitespace or empty lines */ + if (strspn(buf, WHITESPACE_CHARS) == strlen(buf)) + continue; + + romentry_t *entry = (num_rom_entries >= MAX_ROMLAYOUT) ? NULL : &rom_entries[num_rom_entries]; + int ret = parse_entry(name, linecnt, buf, entry); + if (ret < 0) { fclose(romlayout); return 1; } - rom_entries[num_rom_entries].start = strtol(tstr1, (char **)NULL, 16); - rom_entries[num_rom_entries].end = strtol(tstr2, (char **)NULL, 16); - rom_entries[num_rom_entries].included = 0; - rom_entries[num_rom_entries].file = NULL; - num_rom_entries++; + /* Only 0 indicates the successfully parsing of an entry, others are errors or imports. */ + if (ret == 0) + num_rom_entries++; } - - for (i = 0; i < num_rom_entries; i++) { - msg_gdbg("romlayout %08x - %08x named %s\n", - rom_entries[i].start, - rom_entries[i].end, rom_entries[i].name); - } - fclose(romlayout); - + if (num_rom_entries >= MAX_ROMLAYOUT) { + msg_gerr("Found %d entries in layout file which is more than the %i allowed.\n", + num_rom_entries + 1, MAX_ROMLAYOUT); + return 1; + } return 0; } #endif @@ -135,21 +372,6 @@ int register_include_arg(char *name) return 0; }
-/* returns the index of the entry (or a negative value if it is not found) */ -static int find_romentry(char *name) -{ - int i; - msg_gspew("Looking for region "%s"... ", name); - for (i = 0; i < num_rom_entries; i++) { - if (strcmp(rom_entries[i].name, name) == 0) { - msg_gspew("found.\n"); - return i; - } - } - msg_gspew("not found.\n"); - return -1; -} - /* process -i arguments * returns 0 to indicate success, >0 to indicate failure */ @@ -230,6 +452,8 @@ void layout_cleanup(void) num_include_args = 0;
for (i = 0; i < num_rom_entries; i++) { + free(rom_entries[i].name); + rom_entries[i].name = NULL; free(rom_entries[i].file); rom_entries[i].file = NULL; rom_entries[i].included = 0; diff --git a/util/convert_layout_v1_to_v2.sh b/util/convert_layout_v1_to_v2.sh new file mode 100755 index 0000000..712de87 --- /dev/null +++ b/util/convert_layout_v1_to_v2.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# +# Copyright 2013 Stefan Tauner +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +# +# This script converts legacy layout files as understood by flashrom up to version 0.9.7 to format version 2. +# It converts all files given as parameters in place and creates backups (with the suffix ".old") of the old +# contents unless --nobackup is given. +# +# It does... +# - check if the file exists and if its format is already in v2 format +# - prefix addresses with 0x if they have not been already +# - remove superfluous white space (i.e. more than one consecutive space) +# - remove white space from otherwise empty lines + +usage () +{ + echo "Usage: $0 [--nobackup] FILE..." + exit 1 +} + +if [ $# -eq 0 ]; then + usage +fi + +if [ $1 = "--nobackup" ]; then + sed_opt="-i" + shift + if [ $# -eq 0 ]; then + usage + fi +else + sed_opt="-i.old" +fi + +# Test if all files are really there before starting conversion +ret=0 +for f in "$@" ; do + if [ ! -e "$f" ]; then + echo "File not found: $f">&2 + ret=1 + fi +done +if [ "$ret" -ne 0 ]; then + echo "Aborting" + return 1 +fi + +for f in "$@" ; do + if grep -q 'flashrom layout v2\b' "$f" ; then + echo "File already in new format: $f" + continue + fi + + sed $sed_opt -e " + 1i # flashrom layout v2 + s/ *(0x|)([0-9a-fA-F][0-9a-fA-F]*) *: *(0x|)([0-9a-fA-F][0-9a-fA-F]*) */0x\2:0x\4 / + s/ */ / + s/^ *$// + " "$f" + echo "$f done" +done +echo "Done!"