Merge https://gitlab.denx.de/u-boot/custodians/u-boot-net
- DaVinci emac DM work
- NXP driver work
- macb updates for RISC-V
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e27d86f..95fc689 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,7 @@
# Grab our configured image. The source for this is found at:
# https://gitlab.denx.de/u-boot/gitlab-ci-runner
-image: trini/u-boot-gitlab-ci-runner:xenial-20190222-24April2019
+image: trini/u-boot-gitlab-ci-runner:xenial-20190720-24Jul2019
# We run some tests in different order, to catch some failures quicker.
stages:
@@ -172,11 +172,14 @@
tags: [ 'all' ]
stage: testsuites
script:
+ - virtualenv /tmp/venv
+ - . /tmp/venv/bin/activate
+ - pip install pyelftools
- export UBOOT_TRAVIS_BUILD_DIR=`cd .. && pwd`/.bm-work/sandbox_spl;
./tools/buildman/buildman -P sandbox_spl &&
export PYTHONPATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc/pylibfdt";
export PATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc:${PATH}";
- ./tools/binman/binman -t &&
+ ./tools/binman/binman --toolpath ${UBOOT_TRAVIS_BUILD_DIR}/tools test &&
./tools/dtoc/dtoc -t
# Test sandbox with test.py
diff --git a/.travis.yml b/.travis.yml
index f20268b..38fc103 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -32,6 +32,7 @@
- device-tree-compiler
- lzop
- liblz4-tool
+ - lzma-alone
- libisl15
- clang-7
- srecord
@@ -146,7 +147,7 @@
if [[ -n "${TEST_PY_TOOLS}" ]]; then
PYTHONPATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc/pylibfdt"
PATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc:${PATH}"
- ./tools/binman/binman -t &&
+ ./tools/binman/binman --toolpath ${UBOOT_TRAVIS_BUILD_DIR}/tools test &&
./tools/patman/patman --test &&
./tools/buildman/buildman -t &&
PYTHONPATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc/pylibfdt"
diff --git a/Makefile b/Makefile
index 73fdf70..704579b 100644
--- a/Makefile
+++ b/Makefile
@@ -1196,9 +1196,9 @@
# ---------------------------------------------------------------------------
# Use 'make BINMAN_DEBUG=1' to enable debugging
quiet_cmd_binman = BINMAN $@
-cmd_binman = $(srctree)/tools/binman/binman -u -d u-boot.dtb -O . -m \
+cmd_binman = $(srctree)/tools/binman/binman build -u -d u-boot.dtb -O . -m \
-I . -I $(srctree) -I $(srctree)/board/$(BOARDDIR) \
- $(if $(BINMAN_DEBUG),-D) $(BINMAN_$(@F)) $<
+ $(if $(BINMAN_DEBUG),-D) $(BINMAN_$(@F))
OBJCOPYFLAGS_u-boot.ldr.hex := -I binary -O ihex
diff --git a/common/fdt_support.c b/common/fdt_support.c
index a23367b..86de5b8 100644
--- a/common/fdt_support.c
+++ b/common/fdt_support.c
@@ -671,30 +671,33 @@
dma_range[0] = 0;
if (size >= 0x100000000ull)
- dma_range[0] |= FDT_PCI_MEM64;
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_MEM64);
else
- dma_range[0] |= FDT_PCI_MEM32;
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_MEM32);
if (hose->regions[r].flags & PCI_REGION_PREFETCH)
- dma_range[0] |= FDT_PCI_PREFETCH;
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_PREFETCH);
#ifdef CONFIG_SYS_PCI_64BIT
- dma_range[1] = bus_start >> 32;
+ dma_range[1] = cpu_to_fdt32(bus_start >> 32);
#else
dma_range[1] = 0;
#endif
- dma_range[2] = bus_start & 0xffffffff;
+ dma_range[2] = cpu_to_fdt32(bus_start & 0xffffffff);
if (addrcell == 2) {
- dma_range[3] = phys_start >> 32;
- dma_range[4] = phys_start & 0xffffffff;
+ dma_range[3] = cpu_to_fdt32(phys_start >> 32);
+ dma_range[4] = cpu_to_fdt32(phys_start & 0xffffffff);
} else {
- dma_range[3] = phys_start & 0xffffffff;
+ dma_range[3] = cpu_to_fdt32(phys_start & 0xffffffff);
}
if (sizecell == 2) {
- dma_range[3 + addrcell + 0] = size >> 32;
- dma_range[3 + addrcell + 1] = size & 0xffffffff;
+ dma_range[3 + addrcell + 0] =
+ cpu_to_fdt32(size >> 32);
+ dma_range[3 + addrcell + 1] =
+ cpu_to_fdt32(size & 0xffffffff);
} else {
- dma_range[3 + addrcell + 0] = size & 0xffffffff;
+ dma_range[3 + addrcell + 0] =
+ cpu_to_fdt32(size & 0xffffffff);
}
dma_range += (3 + addrcell + sizecell);
@@ -1552,7 +1555,7 @@
prop = fdt_getprop(fdt, node, "reg", &size);
- return prop ? fdt_translate_address(fdt, node, prop) : 0;
+ return prop ? fdt_translate_address(fdt, node, prop) : OF_BAD_ADDR;
}
/*
diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c
index 79b3b04..06a8258 100644
--- a/drivers/clk/clk-uclass.c
+++ b/drivers/clk/clk-uclass.c
@@ -51,6 +51,8 @@
else
clk->id = 0;
+ clk->data = 0;
+
return 0;
}
diff --git a/drivers/core/device.c b/drivers/core/device.c
index 0d15e50..474c164 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -388,7 +388,8 @@
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
- if (dev->parent && device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) {
+ if (CONFIG_IS_ENABLED(POWER_DOMAIN) && dev->parent &&
+ device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) {
if (!power_domain_get(dev, &pd))
power_domain_on(&pd);
}
@@ -409,10 +410,16 @@
goto fail;
}
- /* Process 'assigned-{clocks/clock-parents/clock-rates}' properties */
- ret = clk_set_defaults(dev);
- if (ret)
- goto fail;
+ /* Only handle devices that have a valid ofnode */
+ if (dev_of_valid(dev)) {
+ /*
+ * Process 'assigned-{clocks/clock-parents/clock-rates}'
+ * properties
+ */
+ ret = clk_set_defaults(dev);
+ if (ret)
+ goto fail;
+ }
if (drv->probe) {
ret = drv->probe(dev);
diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c
index 179a644..2ac73af 100644
--- a/drivers/core/ofnode.c
+++ b/drivers/core/ofnode.c
@@ -884,5 +884,5 @@
if (value)
return ofnode_write_string(node, "status", "okay");
else
- return ofnode_write_string(node, "status", "disable");
+ return ofnode_write_string(node, "status", "disabled");
}
diff --git a/drivers/timer/timer-uclass.c b/drivers/timer/timer-uclass.c
index 12ee6eb..97a4c74 100644
--- a/drivers/timer/timer-uclass.c
+++ b/drivers/timer/timer-uclass.c
@@ -48,6 +48,10 @@
int err;
ulong ret;
+ /* It is possible that a timer device has a null ofnode */
+ if (!dev_of_valid(dev))
+ return 0;
+
err = clk_get_by_index(dev, 0, &timer_clk);
if (!err) {
ret = clk_get_rate(&timer_clk);
diff --git a/fs/cbfs/cbfs.c b/fs/cbfs/cbfs.c
index 7b2513c..af4d3c5 100644
--- a/fs/cbfs/cbfs.c
+++ b/fs/cbfs/cbfs.c
@@ -55,7 +55,7 @@
memcpy(&dest->magic, &src->magic, sizeof(dest->magic));
dest->len = be32_to_cpu(src->len);
dest->type = be32_to_cpu(src->type);
- dest->checksum = be32_to_cpu(src->checksum);
+ dest->attributes_offset = be32_to_cpu(src->attributes_offset);
dest->offset = be32_to_cpu(src->offset);
}
@@ -108,7 +108,7 @@
newNode->name = (char *)fileHeader +
sizeof(struct cbfs_fileheader);
newNode->name_length = name_len;
- newNode->checksum = header.checksum;
+ newNode->attributes_offset = header.attributes_offset;
step = header.len;
if (step % align)
diff --git a/include/cbfs.h b/include/cbfs.h
index bd1bf75..b8d1dab 100644
--- a/include/cbfs.h
+++ b/include/cbfs.h
@@ -40,6 +40,17 @@
CBFS_TYPE_CMOS_LAYOUT = 0x01aa
};
+enum {
+ CBFS_HEADER_MAGIC = 0x4f524243,
+};
+
+/**
+ * struct cbfs_header - header at the start of a CBFS region
+ *
+ * All fields use big-endian format.
+ *
+ * @magic: Magic number (CBFS_HEADER_MAGIC)
+ */
struct cbfs_header {
u32 magic;
u32 version;
@@ -54,7 +65,8 @@
u8 magic[8];
u32 len;
u32 type;
- u32 checksum;
+ /* offset to struct cbfs_file_attribute or 0 */
+ u32 attributes_offset;
u32 offset;
} __packed;
@@ -65,7 +77,7 @@
u32 data_length;
char *name;
u32 name_length;
- u32 checksum;
+ u32 attributes_offset;
} __packed;
extern enum cbfs_result file_cbfs_result;
diff --git a/include/dm/read.h b/include/dm/read.h
index 62d4be6..6ecd062 100644
--- a/include/dm/read.h
+++ b/include/dm/read.h
@@ -227,7 +227,7 @@
/**
* dev_read_name() - get the name of a device's node
*
- * @node: valid node to look up
+ * @dev: Device to read from
* @return name of node
*/
const char *dev_read_name(struct udevice *dev);
diff --git a/include/dm/uclass.h b/include/dm/uclass.h
index 1bc62d5..484d166 100644
--- a/include/dm/uclass.h
+++ b/include/dm/uclass.h
@@ -297,7 +297,7 @@
*
* The device returned is probed if necessary, and ready for use
*
- * This function is useful to start iterating through a list of devices which
+ * This function is useful to iterate through a list of devices which
* are functioning correctly and can be probed.
*
* @devp: On entry, pointer to device to lookup. On exit, returns pointer
diff --git a/test/run b/test/run
index 55a6649..d635622 100755
--- a/test/run
+++ b/test/run
@@ -33,12 +33,14 @@
-k test_ut
# Set up a path to dtc (device-tree compiler) and libfdt.py, a library it
-# provides and which is built by the sandbox_spl config.
+# provides and which is built by the sandbox_spl config. Also set up the path
+# to tools build by the build.
DTC_DIR=build-sandbox_spl/scripts/dtc
export PYTHONPATH=${DTC_DIR}/pylibfdt
export DTC=${DTC_DIR}/dtc
+TOOLS_DIR=build-sandbox_spl/tools
-run_test "binman" ./tools/binman/binman -t
+run_test "binman" ./tools/binman/binman --toolpath ${TOOLS_DIR} test
run_test "patman" ./tools/patman/patman --test
[ "$1" == "quick" ] && skip=--skip-net-tests
@@ -49,7 +51,8 @@
# This needs you to set up Python test coverage tools.
# To enable Python test coverage on Debian-type distributions (e.g. Ubuntu):
# $ sudo apt-get install python-pytest python-coverage
-run_test "binman code coverage" ./tools/binman/binman -T
+export PATH=$PATH:${TOOLS_DIR}
+run_test "binman code coverage" ./tools/binman/binman test -T
run_test "dtoc code coverage" ./tools/dtoc/dtoc -T
run_test "fdt code coverage" ./tools/dtoc/test_fdt -T
diff --git a/tools/Makefile b/tools/Makefile
index 33e90a8..87d81a3 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -175,6 +175,9 @@
ifdtool-objs := $(LIBFDT_OBJS) ifdtool.o
hostprogs-$(CONFIG_X86) += ifdtool
+ifwitool-objs := ifwitool.o
+hostprogs-$(CONFIG_X86)$(CONFIG_SANDBOX) += ifwitool
+
hostprogs-$(CONFIG_MX23) += mxsboot
hostprogs-$(CONFIG_MX28) += mxsboot
HOSTCFLAGS_mxsboot.o := -pedantic
diff --git a/tools/binman/README b/tools/binman/README
index ac193f1..756c6a0 100644
--- a/tools/binman/README
+++ b/tools/binman/README
@@ -36,10 +36,9 @@
they are included, by adding a Python plug-in. The device tree is available
to U-Boot at run-time so that the images can be interpreted.
-Binman does not yet update the device tree with the final location of
-everything when it is done. A simple C structure could be generated for
-constrained environments like SPL (using dtoc) but this is also not
-implemented.
+Binman can update the device tree with the final location of everything when it
+is done. Entry positions can be provided to U-Boot SPL as run-time symbols,
+avoiding device-tree code overhead.
Binman can also support incorporating filesystems in the image if required.
For example x86 platforms may use CBFS in some cases.
@@ -181,9 +180,14 @@
Running binman
--------------
+First install prerequisites, e.g.
+
+ sudo apt-get install python-pyelftools python3-pyelftools lzma-alone \
+ liblz4-tool
+
Type:
- binman -b <board_name>
+ binman build -b <board_name>
to build an image for a board. The board name is the same name used when
configuring U-Boot (e.g. for sandbox_defconfig the board name is 'sandbox').
@@ -191,7 +195,7 @@
Or you can specify this explicitly:
- binman -I <build_path>
+ binman build -I <build_path>
where <build_path> is the build directory containing the output of the U-Boot
build.
@@ -335,6 +339,10 @@
limited by the size of the image/section and the position of the next
entry.
+compress:
+ Sets the compression algortihm to use (for blobs only). See the entry
+ documentation for details.
+
The attributes supported for images and sections are described below. Several
are similar to those for entries.
@@ -479,8 +487,93 @@
For details on the various entry types supported by binman and how to use them,
see README.entries. This is generated from the source code using:
+ binman entry-docs >tools/binman/README.entries
+
+
+Listing images
+--------------
+
+It is possible to list the entries in an existing firmware image created by
+binman, provided that there is an 'fdtmap' entry in the image. For example:
+
+ $ binman ls -i image.bin
+ Name Image-pos Size Entry-type Offset Uncomp-size
+ ----------------------------------------------------------------------
+ main-section c00 section 0
+ u-boot 0 4 u-boot 0
+ section 5fc section 4
+ cbfs 100 400 cbfs 0
+ u-boot 138 4 u-boot 38
+ u-boot-dtb 180 108 u-boot-dtb 80 3b5
+ u-boot-dtb 500 1ff u-boot-dtb 400 3b5
+ fdtmap 6fc 381 fdtmap 6fc
+ image-header bf8 8 image-header bf8
+
+This shows the hierarchy of the image, the position, size and type of each
+entry, the offset of each entry within its parent and the uncompressed size if
+the entry is compressed.
+
- binman -E >tools/binman/README.entries
+It is also possible to list just some files in an image, e.g.
+ $ binman ls -i image.bin section/cbfs
+ Name Image-pos Size Entry-type Offset Uncomp-size
+ --------------------------------------------------------------------
+ cbfs 100 400 cbfs 0
+ u-boot 138 4 u-boot 38
+ u-boot-dtb 180 108 u-boot-dtb 80 3b5
+
+or with wildcards:
+
+ $ binman ls -i image.bin "*cb*" "*head*"
+ Name Image-pos Size Entry-type Offset Uncomp-size
+ ----------------------------------------------------------------------
+ cbfs 100 400 cbfs 0
+ u-boot 138 4 u-boot 38
+ u-boot-dtb 180 108 u-boot-dtb 80 3b5
+ image-header bf8 8 image-header bf8
+
+
+Extracting files from images
+----------------------------
+
+You can extract files from an existing firmware image created by binman,
+provided that there is an 'fdtmap' entry in the image. For example:
+
+ $ binman extract -i image.bin section/cbfs/u-boot
+
+which will write the uncompressed contents of that entry to the file 'u-boot' in
+the current directory. You can also extract to a particular file, in this case
+u-boot.bin:
+
+ $ binman extract -i image.bin section/cbfs/u-boot -f u-boot.bin
+
+It is possible to extract all files into a destination directory, which will
+put files in subdirectories matching the entry hierarchy:
+
+ $ binman extract -i image.bin -O outdir
+
+or just a selection:
+
+ $ binman extract -i image.bin "*u-boot*" -O outdir
+
+
+Logging
+-------
+
+Binman normally operates silently unless there is an error, in which case it
+just displays the error. The -D/--debug option can be used to create a full
+backtrace when errors occur.
+
+Internally binman logs some output while it is running. This can be displayed
+by increasing the -v/--verbosity from the default of 1:
+
+ 0: silent
+ 1: warnings only
+ 2: notices (important messages)
+ 3: info about major operations
+ 4: detailed information about each operation
+ 5: debug (all output)
+
Hashing Entries
---------------
@@ -558,7 +651,8 @@
The default implementatoin does nothing. This can be overriden to adjust the
contents of an entry in some way. For example, it would be possible to create
an entry containing a hash of the contents of some other entries. At this
-stage the offset and size of entries should not be adjusted.
+stage the offset and size of entries should not be adjusted unless absolutely
+necessary, since it requires a repack (going back to PackEntries()).
10. WriteSymbols() - write the value of symbols into the U-Boot SPL binary.
See 'Access to binman entry offsets at run time' below for a description of
@@ -634,20 +728,27 @@
the device tree. These can be used by U-Boot at run-time to find the location
of each entry.
+Alternatively, an FDT map entry can be used to add a special FDT containing
+just the information about the image. This is preceded by a magic string so can
+be located anywhere in the image. An image header (typically at the start or end
+of the image) can be used to point to the FDT map. See fdtmap and image-header
+entries for more information.
+
Compression
-----------
Binman support compression for 'blob' entries (those of type 'blob' and
-derivatives). To enable this for an entry, add a 'compression' property:
+derivatives). To enable this for an entry, add a 'compress' property:
blob {
filename = "datafile";
- compression = "lz4";
+ compress = "lz4";
};
The entry will then contain the compressed data, using the 'lz4' compression
-algorithm. Currently this is the only one that is supported.
+algorithm. Currently this is the only one that is supported. The uncompressed
+size is written to the node in an 'uncomp-size' property, if -u is used.
@@ -691,15 +792,25 @@
typically for filenames.
+External tools
+--------------
+
+Binman can make use of external command-line tools to handle processing of
+entry contents or to generate entry contents. These tools are executed using
+the 'tools' module's Run() method. The tools generally must exist on the PATH,
+but the --toolpath option can be used to specify additional search paths to
+use. This option can be specified multiple times to add more than one path.
+
+
Code coverage
-------------
Binman is a critical tool and is designed to be very testable. Entry
-implementations target 100% test coverage. Run 'binman -T' to check this.
+implementations target 100% test coverage. Run 'binman test -T' to check this.
To enable Python test coverage on Debian-type distributions (e.g. Ubuntu):
- $ sudo apt-get install python-coverage python-pytest
+ $ sudo apt-get install python-coverage python3-coverage python-pytest
Concurrent tests
@@ -716,6 +827,14 @@
being used (-T) since they are incompatible.
+Debugging tests
+---------------
+
+Sometimes when debugging tests it is useful to keep the input and output
+directories so they can be examined later. Use -X or --test-preserve-dirs for
+this.
+
+
Advanced Features / Technical docs
----------------------------------
@@ -788,13 +907,12 @@
- Use of-platdata to make the information available to code that is unable
to use device tree (such as a very small SPL image)
- Allow easy building of images by specifying just the board name
-- Produce a full Python binding for libfdt (for upstream). This is nearing
- completion but some work remains
-- Add an option to decode an image into the constituent binaries
- Support building an image for a board (-b) more completely, with a
configurable build directory
-- Consider making binman work with buildman, although if it is used in the
- Makefile, this will be automatic
+- Support updating binaries in an image (with no size change / repacking)
+- Support updating binaries in an image (with repacking)
+- Support adding FITs to an image
+- Support for ARM Trusted Firmware (ATF)
--
Simon Glass <sjg@chromium.org>
diff --git a/tools/binman/README.entries b/tools/binman/README.entries
index 357946d..7ce88ee 100644
--- a/tools/binman/README.entries
+++ b/tools/binman/README.entries
@@ -60,6 +60,158 @@
+Entry: cbfs: Entry containing a Coreboot Filesystem (CBFS)
+----------------------------------------------------------
+
+A CBFS provides a way to group files into a group. It has a simple directory
+structure and allows the position of individual files to be set, since it is
+designed to support execute-in-place in an x86 SPI-flash device. Where XIP
+is not used, it supports compression and storing ELF files.
+
+CBFS is used by coreboot as its way of orgnanising SPI-flash contents.
+
+The contents of the CBFS are defined by subnodes of the cbfs entry, e.g.:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+
+This creates a CBFS 1MB in size two files in it: u-boot.bin and u-boot.dtb.
+Note that the size is required since binman does not support calculating it.
+The contents of each entry is just what binman would normally provide if it
+were not a CBFS node. A blob type can be used to import arbitrary files as
+with the second subnode below:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot {
+ cbfs-name = "BOOT";
+ cbfs-type = "raw";
+ };
+
+ dtb {
+ type = "blob";
+ filename = "u-boot.dtb";
+ cbfs-type = "raw";
+ cbfs-compress = "lz4";
+ cbfs-offset = <0x100000>;
+ };
+ };
+
+This creates a CBFS 1MB in size with u-boot.bin (named "BOOT") and
+u-boot.dtb (named "dtb") and compressed with the lz4 algorithm.
+
+
+Properties supported in the top-level CBFS node:
+
+cbfs-arch:
+ Defaults to "x86", but you can specify the architecture if needed.
+
+
+Properties supported in the CBFS entry subnodes:
+
+cbfs-name:
+ This is the name of the file created in CBFS. It defaults to the entry
+ name (which is the node name), but you can override it with this
+ property.
+
+cbfs-type:
+ This is the CBFS file type. The following are supported:
+
+ raw:
+ This is a 'raw' file, although compression is supported. It can be
+ used to store any file in CBFS.
+
+ stage:
+ This is an ELF file that has been loaded (i.e. mapped to memory), so
+ appears in the CBFS as a flat binary. The input file must be an ELF
+ image, for example this puts "u-boot" (the ELF image) into a 'stage'
+ entry:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot-elf {
+ cbfs-name = "BOOT";
+ cbfs-type = "stage";
+ };
+ };
+
+ You can use your own ELF file with something like:
+
+ cbfs {
+ size = <0x100000>;
+ something {
+ type = "blob";
+ filename = "cbfs-stage.elf";
+ cbfs-type = "stage";
+ };
+ };
+
+ As mentioned, the file is converted to a flat binary, so it is
+ equivalent to adding "u-boot.bin", for example, but with the load and
+ start addresses specified by the ELF. At present there is no option
+ to add a flat binary with a load/start address, similar to the
+ 'add-flat-binary' option in cbfstool.
+
+cbfs-offset:
+ This is the offset of the file's data within the CBFS. It is used to
+ specify where the file should be placed in cases where a fixed position
+ is needed. Typical uses are for code which is not relocatable and must
+ execute in-place from a particular address. This works because SPI flash
+ is generally mapped into memory on x86 devices. The file header is
+ placed before this offset so that the data start lines up exactly with
+ the chosen offset. If this property is not provided, then the file is
+ placed in the next available spot.
+
+The current implementation supports only a subset of CBFS features. It does
+not support other file types (e.g. payload), adding multiple files (like the
+'files' entry with a pattern supported by binman), putting files at a
+particular offset in the CBFS and a few other things.
+
+Of course binman can create images containing multiple CBFSs, simply by
+defining these in the binman config:
+
+
+ binman {
+ size = <0x800000>;
+ cbfs {
+ offset = <0x100000>;
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+
+ cbfs2 {
+ offset = <0x700000>;
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ image {
+ type = "blob";
+ filename = "image.jpg";
+ };
+ };
+ };
+
+This creates an 8MB image with two CBFSs, one at offset 1MB, one at 7MB,
+both of size 1MB.
+
+
+
Entry: cros-ec-rw: A blob entry which contains a Chromium OS read-write EC image
--------------------------------------------------------------------------------
@@ -69,7 +221,45 @@
This entry holds a Chromium OS EC (embedded controller) image, for use in
updating the EC on startup via software sync.
+
+
+Entry: fdtmap: An entry which contains an FDT map
+-------------------------------------------------
+
+Properties / Entry arguments:
+ None
+
+An FDT map is just a header followed by an FDT containing a list of all the
+entries in the image.
+
+The header is the string _FDTMAP_ followed by 8 unused bytes.
+When used, this entry will be populated with an FDT map which reflects the
+entries in the current image. Hierarchy is preserved, and all offsets and
+sizes are included.
+
+Note that the -u option must be provided to ensure that binman updates the
+FDT with the position of each entry.
+
+Example output for a simple image with U-Boot and an FDT map:
+
+/ {
+ size = <0x00000112>;
+ image-pos = <0x00000000>;
+ offset = <0x00000000>;
+ u-boot {
+ size = <0x00000004>;
+ image-pos = <0x00000000>;
+ offset = <0x00000000>;
+ };
+ fdtmap {
+ size = <0x0000010e>;
+ image-pos = <0x00000004>;
+ offset = <0x00000004>;
+ };
+};
+
+
Entry: files: Entry containing a set of files
---------------------------------------------
@@ -141,6 +331,25 @@
+Entry: image-header: An entry which contains a pointer to the FDT map
+---------------------------------------------------------------------
+
+Properties / Entry arguments:
+ location: Location of header ("start" or "end" of image). This is
+ optional. If omitted then the entry must have an offset property.
+
+This adds an 8-byte entry to the start or end of the image, pointing to the
+location of the FDT map. The format is a magic number followed by an offset
+from the start or end of the image, in twos-compliment format.
+
+This entry must be in the top-level part of the image.
+
+NOTE: If the location is at the start/end, you will probably need to specify
+sort-by-offset for the image, unless you actually put the image header
+first/last in the entry list.
+
+
+
Entry: intel-cmc: Entry containing an Intel Chipset Micro Code (CMC) file
-------------------------------------------------------------------------
@@ -192,6 +401,34 @@
+Entry: intel-ifwi: Entry containing an Intel Integrated Firmware Image (IFWI) file
+----------------------------------------------------------------------------------
+
+Properties / Entry arguments:
+ - filename: Filename of file to read into entry. This is either the
+ IFWI file itself, or a file that can be converted into one using a
+ tool
+ - convert-fit: If present this indicates that the ifwitool should be
+ used to convert the provided file into a IFWI.
+
+This file contains code and data used by the SoC that is required to make
+it work. It includes U-Boot TPL, microcode, things related to the CSE
+(Converged Security Engine, the microcontroller that loads all the firmware)
+and other items beyond the wit of man.
+
+A typical filename is 'ifwi.bin' for an IFWI file, or 'fitimage.bin' for a
+file that will be converted to an IFWI.
+
+The position of this entry is generally set by the intel-descriptor entry.
+
+The contents of the IFWI are specified by the subnodes of the IFWI node.
+Each subnode describes an entry which is placed into the IFWFI with a given
+sub-partition (and optional entry name).
+
+See README.x86 for information about x86 binary blobs.
+
+
+
Entry: intel-me: Entry containing an Intel Management Engine (ME) file
----------------------------------------------------------------------
@@ -206,6 +443,8 @@
A typical filename is 'me.bin'.
+The position of this entry is generally set by the intel-descriptor entry.
+
See README.x86 for information about x86 binary blobs.
@@ -282,16 +521,21 @@
-------------------------------------------------
Properties / Entry arguments: (see binman README for more information)
- - size: Size of section in bytes
- - align-size: Align size to a particular power of two
- - pad-before: Add padding before the entry
- - pad-after: Add padding after the entry
- - pad-byte: Pad byte to use when padding
- - sort-by-offset: Reorder the entries by offset
- - end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
- - name-prefix: Adds a prefix to the name of every entry in the section
+ pad-byte: Pad byte to use when padding
+ sort-by-offset: True if entries should be sorted by offset, False if
+ they must be in-order in the device tree description
+ end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
+ skip-at-start: Number of bytes before the first entry starts. These
+ effectively adjust the starting offset of entries. For example,
+ if this is 16, then the first entry would start at 16. An entry
+ with offset = 20 would in fact be written at offset 4 in the image
+ file, since the first 16 bytes are skipped when writing.
+ name-prefix: Adds a prefix to the name of every entry in the section
when writing out the map
+Since a section is also an entry, it inherits all the properies of entries
+too.
+
A section is an entry which can contain other entries, thus allowing
hierarchical images to be created. See 'Sections and hierarchical images'
in the binman README for more information.
@@ -310,6 +554,8 @@
that contains the string to place in the entry
<xxx> (actual name is the value of text-label): contains the string to
place in the entry.
+ <text>: The text to place in the entry (overrides the above mechanism).
+ This is useful when the text is constant.
Example node:
@@ -332,6 +578,13 @@
message = "a message directly in the node"
};
+or just:
+
+ text {
+ size = <8>;
+ text = "some text directly in the node"
+ };
+
The text is not itself nul-terminated. This can be achieved, if required,
by setting the size of the entry to something larger than the text.
@@ -485,7 +738,7 @@
-------------------------------------------
Properties / Entry arguments:
- - filename: Filename of SPL u-boot (default 'spl/u-boot')
+ - filename: Filename of SPL u-boot (default 'spl/u-boot-spl')
This is the U-Boot SPL ELF image. It does not include a device tree but can
be relocated to any address for execution.
@@ -563,6 +816,17 @@
+Entry: u-boot-tpl-elf: U-Boot TPL ELF image
+-------------------------------------------
+
+Properties / Entry arguments:
+ - filename: Filename of TPL u-boot (default 'tpl/u-boot-tpl')
+
+This is the U-Boot TPL ELF image. It does not include a device tree but can
+be relocated to any address for execution.
+
+
+
Entry: u-boot-tpl-with-ucode-ptr: U-Boot TPL with embedded microcode pointer
----------------------------------------------------------------------------
diff --git a/tools/binman/binman.py b/tools/binman/binman.py
index aad2e9c..8bd5868 100755
--- a/tools/binman/binman.py
+++ b/tools/binman/binman.py
@@ -11,23 +11,32 @@
from __future__ import print_function
+from distutils.sysconfig import get_python_lib
import glob
import multiprocessing
import os
+import site
import sys
import traceback
import unittest
-# Bring in the patman and dtoc libraries
+# Bring in the patman and dtoc libraries (but don't override the first path
+# in PYTHONPATH)
our_path = os.path.dirname(os.path.realpath(__file__))
for dirname in ['../patman', '../dtoc', '..', '../concurrencytest']:
- sys.path.insert(0, os.path.join(our_path, dirname))
+ sys.path.insert(2, os.path.join(our_path, dirname))
# Bring in the libfdt module
-sys.path.insert(0, 'scripts/dtc/pylibfdt')
-sys.path.insert(0, os.path.join(our_path,
+sys.path.insert(2, 'scripts/dtc/pylibfdt')
+sys.path.insert(2, os.path.join(our_path,
'../../build-sandbox_spl/scripts/dtc/pylibfdt'))
+# When running under python-coverage on Ubuntu 16.04, the dist-packages
+# directories are dropped from the python path. Add them in so that we can find
+# the elffile module. We could use site.getsitepackages() here but unfortunately
+# that is not available in a virtualenv.
+sys.path.append(get_python_lib())
+
import cmdline
import command
use_concurrent = True
@@ -38,15 +47,23 @@
import control
import test_util
-def RunTests(debug, processes, args):
+def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath):
"""Run the functional tests and any embedded doctests
Args:
debug: True to enable debugging, which shows a full stack trace on error
- args: List of positional args provided to binman. This can hold a test
- name to execute (as in 'binman -t testSections', for example)
+ verbosity: Verbosity level to use
+ test_preserve_dirs: True to preserve the input directory used by tests
+ so that it can be examined afterwards (only useful for debugging
+ tests). If a single test is selected (in args[0]) it also preserves
+ the output directory for this test. Both directories are displayed
+ on the command line.
processes: Number of processes to use to run tests (None=same as #CPUs)
+ args: List of positional args provided to binman. This can hold a test
+ name to execute (as in 'binman test testSections', for example)
+ toolpath: List of paths to use for tools
"""
+ import cbfs_util_test
import elf_test
import entry_test
import fdt_test
@@ -63,8 +80,11 @@
sys.argv = [sys.argv[0]]
if debug:
sys.argv.append('-D')
- if debug:
- sys.argv.append('-D')
+ if verbosity:
+ sys.argv.append('-v%d' % verbosity)
+ if toolpath:
+ for path in toolpath:
+ sys.argv += ['--toolpath', path]
# Run the entry tests first ,since these need to be the first to import the
# 'entry' module.
@@ -72,7 +92,14 @@
suite = unittest.TestSuite()
loader = unittest.TestLoader()
for module in (entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt,
- elf_test.TestElf, image_test.TestImage):
+ elf_test.TestElf, image_test.TestImage,
+ cbfs_util_test.TestCbfs):
+ # Test the test module about our arguments, if it is interested
+ if hasattr(module, 'setup_test_args'):
+ setup_test_args = getattr(module, 'setup_test_args')
+ setup_test_args(preserve_indir=test_preserve_dirs,
+ preserve_outdirs=test_preserve_dirs and test_name is not None,
+ toolpath=toolpath, verbosity=verbosity)
if test_name:
try:
suite.addTests(loader.loadTestsFromName(test_name, module))
@@ -104,9 +131,14 @@
print(test.id(), err)
for test, err in result.failures:
print(err, result.failures)
+ if result.skipped:
+ print('%d binman test%s SKIPPED:' %
+ (len(result.skipped), 's' if len(result.skipped) > 1 else ''))
+ for skip_info in result.skipped:
+ print('%s: %s' % (skip_info[0], skip_info[1]))
if result.errors or result.failures:
- print('binman tests FAILED')
- return 1
+ print('binman tests FAILED')
+ return 1
return 0
def GetEntryModules(include_testing=True):
@@ -127,38 +159,36 @@
for item in glob_list if '_testing' not in item])
test_util.RunTestCoverage('tools/binman/binman.py', None,
['*test*', '*binman.py', 'tools/patman/*', 'tools/dtoc/*'],
- options.build_dir, all_set)
+ args.build_dir, all_set)
-def RunBinman(options, args):
+def RunBinman(args):
"""Main entry point to binman once arguments are parsed
Args:
- options: Command-line options
- args: Non-option arguments
+ args: Command line arguments Namespace object
"""
ret_code = 0
- # For testing: This enables full exception traces.
- #options.debug = True
-
- if not options.debug:
+ if not args.debug:
sys.tracebacklimit = 0
- if options.test:
- ret_code = RunTests(options.debug, options.processes, args[1:])
-
- elif options.test_coverage:
- RunTestCoverage()
+ if args.cmd == 'test':
+ if args.test_coverage:
+ RunTestCoverage()
+ else:
+ ret_code = RunTests(args.debug, args.verbosity, args.processes,
+ args.test_preserve_dirs, args.tests,
+ args.toolpath)
- elif options.entry_docs:
+ elif args.cmd == 'entry-docs':
control.WriteEntryDocs(GetEntryModules())
else:
try:
- ret_code = control.Binman(options, args)
+ ret_code = control.Binman(args)
except Exception as e:
print('binman: %s' % e)
- if options.debug:
+ if args.debug:
print()
traceback.print_exc()
ret_code = 1
@@ -166,6 +196,7 @@
if __name__ == "__main__":
- (options, args) = cmdline.ParseArgs(sys.argv)
- ret_code = RunBinman(options, args)
+ args = cmdline.ParseArgs(sys.argv[1:])
+
+ ret_code = RunBinman(args)
sys.exit(ret_code)
diff --git a/tools/binman/bsection.py b/tools/binman/bsection.py
deleted file mode 100644
index 03dfa2f..0000000
--- a/tools/binman/bsection.py
+++ /dev/null
@@ -1,464 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0+
-# Copyright (c) 2018 Google, Inc
-# Written by Simon Glass <sjg@chromium.org>
-#
-# Base class for sections (collections of entries)
-#
-
-from __future__ import print_function
-
-from collections import OrderedDict
-import sys
-
-import fdt_util
-import re
-import state
-import tools
-
-class Section(object):
- """A section which contains multiple entries
-
- A section represents a collection of entries. There must be one or more
- sections in an image. Sections are used to group entries together.
-
- Attributes:
- _node: Node object that contains the section definition in device tree
- _parent_section: Parent Section object which created this Section
- _size: Section size in bytes, or None if not known yet
- _align_size: Section size alignment, or None
- _pad_before: Number of bytes before the first entry starts. This
- effectively changes the place where entry offset 0 starts
- _pad_after: Number of bytes after the last entry ends. The last
- entry will finish on or before this boundary
- _pad_byte: Byte to use to pad the section where there is no entry
- _sort: True if entries should be sorted by offset, False if they
- must be in-order in the device tree description
- _skip_at_start: Number of bytes before the first entry starts. These
- effectively adjust the starting offset of entries. For example,
- if _pad_before is 16, then the first entry would start at 16.
- An entry with offset = 20 would in fact be written at offset 4
- in the image file.
- _end_4gb: Indicates that the section ends at the 4GB boundary. This is
- used for x86 images, which want to use offsets such that a memory
- address (like 0xff800000) is the first entry offset. This causes
- _skip_at_start to be set to the starting memory address.
- _name_prefix: Prefix to add to the name of all entries within this
- section
- _entries: OrderedDict() of entries
- """
- def __init__(self, name, parent_section, node, image, test=False):
- global entry
- global Entry
- import entry
- from entry import Entry
-
- self._parent_section = parent_section
- self._name = name
- self._node = node
- self._image = image
- self._offset = None
- self._size = None
- self._align_size = None
- self._pad_before = 0
- self._pad_after = 0
- self._pad_byte = 0
- self._sort = False
- self._skip_at_start = None
- self._end_4gb = False
- self._name_prefix = ''
- self._entries = OrderedDict()
- self._image_pos = None
- if not test:
- self._ReadNode()
- self._ReadEntries()
-
- def _ReadNode(self):
- """Read properties from the section node"""
- self._offset = fdt_util.GetInt(self._node, 'offset')
- self._size = fdt_util.GetInt(self._node, 'size')
- self._align_size = fdt_util.GetInt(self._node, 'align-size')
- if tools.NotPowerOfTwo(self._align_size):
- self._Raise("Alignment size %s must be a power of two" %
- self._align_size)
- self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
- self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
- self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
- self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
- self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
- self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
- if self._end_4gb:
- if not self._size:
- self._Raise("Section size must be provided when using end-at-4gb")
- if self._skip_at_start is not None:
- self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
- else:
- self._skip_at_start = 0x100000000 - self._size
- else:
- if self._skip_at_start is None:
- self._skip_at_start = 0
- self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
-
- def _ReadEntries(self):
- for node in self._node.subnodes:
- if node.name == 'hash':
- continue
- entry = Entry.Create(self, node)
- entry.SetPrefix(self._name_prefix)
- self._entries[node.name] = entry
-
- def GetFdtSet(self):
- """Get the set of device tree files used by this image"""
- fdt_set = set()
- for entry in self._entries.values():
- fdt_set.update(entry.GetFdtSet())
- return fdt_set
-
- def SetOffset(self, offset):
- self._offset = offset
-
- def ExpandEntries(self):
- for entry in self._entries.values():
- entry.ExpandEntries()
-
- def AddMissingProperties(self):
- """Add new properties to the device tree as needed for this entry"""
- for prop in ['offset', 'size', 'image-pos']:
- if not prop in self._node.props:
- state.AddZeroProp(self._node, prop)
- state.CheckAddHashProp(self._node)
- for entry in self._entries.values():
- entry.AddMissingProperties()
-
- def SetCalculatedProperties(self):
- state.SetInt(self._node, 'offset', self._offset or 0)
- state.SetInt(self._node, 'size', self._size)
- image_pos = self._image_pos
- if self._parent_section:
- image_pos -= self._parent_section.GetRootSkipAtStart()
- state.SetInt(self._node, 'image-pos', image_pos)
- for entry in self._entries.values():
- entry.SetCalculatedProperties()
-
- def ProcessFdt(self, fdt):
- todo = self._entries.values()
- for passnum in range(3):
- next_todo = []
- for entry in todo:
- if not entry.ProcessFdt(fdt):
- next_todo.append(entry)
- todo = next_todo
- if not todo:
- break
- if todo:
- self._Raise('Internal error: Could not complete processing of Fdt: '
- 'remaining %s' % todo)
- return True
-
- def CheckSize(self):
- """Check that the section contents does not exceed its size, etc."""
- contents_size = 0
- for entry in self._entries.values():
- contents_size = max(contents_size, entry.offset + entry.size)
-
- contents_size -= self._skip_at_start
-
- size = self._size
- if not size:
- size = self._pad_before + contents_size + self._pad_after
- size = tools.Align(size, self._align_size)
-
- if self._size and contents_size > self._size:
- self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
- (contents_size, contents_size, self._size, self._size))
- if not self._size:
- self._size = size
- if self._size != tools.Align(self._size, self._align_size):
- self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
- (self._size, self._size, self._align_size, self._align_size))
- return size
-
- def _Raise(self, msg):
- """Raises an error for this section
-
- Args:
- msg: Error message to use in the raise string
- Raises:
- ValueError()
- """
- raise ValueError("Section '%s': %s" % (self._node.path, msg))
-
- def GetPath(self):
- """Get the path of an image (in the FDT)
-
- Returns:
- Full path of the node for this image
- """
- return self._node.path
-
- def FindEntryType(self, etype):
- """Find an entry type in the section
-
- Args:
- etype: Entry type to find
- Returns:
- entry matching that type, or None if not found
- """
- for entry in self._entries.values():
- if entry.etype == etype:
- return entry
- return None
-
- def GetEntryContents(self):
- """Call ObtainContents() for each entry
-
- This calls each entry's ObtainContents() a few times until they all
- return True. We stop calling an entry's function once it returns
- True. This allows the contents of one entry to depend on another.
-
- After 3 rounds we give up since it's likely an error.
- """
- todo = self._entries.values()
- for passnum in range(3):
- next_todo = []
- for entry in todo:
- if not entry.ObtainContents():
- next_todo.append(entry)
- todo = next_todo
- if not todo:
- break
- if todo:
- self._Raise('Internal error: Could not complete processing of '
- 'contents: remaining %s' % todo)
- return True
-
- def _SetEntryOffsetSize(self, name, offset, size):
- """Set the offset and size of an entry
-
- Args:
- name: Entry name to update
- offset: New offset
- size: New size
- """
- entry = self._entries.get(name)
- if not entry:
- self._Raise("Unable to set offset/size for unknown entry '%s'" %
- name)
- entry.SetOffsetSize(self._skip_at_start + offset, size)
-
- def GetEntryOffsets(self):
- """Handle entries that want to set the offset/size of other entries
-
- This calls each entry's GetOffsets() method. If it returns a list
- of entries to update, it updates them.
- """
- for entry in self._entries.values():
- offset_dict = entry.GetOffsets()
- for name, info in offset_dict.items():
- self._SetEntryOffsetSize(name, *info)
-
- def PackEntries(self):
- """Pack all entries into the section"""
- offset = self._skip_at_start
- for entry in self._entries.values():
- offset = entry.Pack(offset)
- self._size = self.CheckSize()
-
- def _SortEntries(self):
- """Sort entries by offset"""
- entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
- self._entries.clear()
- for entry in entries:
- self._entries[entry._node.name] = entry
-
- def _ExpandEntries(self):
- """Expand any entries that are permitted to"""
- exp_entry = None
- for entry in self._entries.values():
- if exp_entry:
- exp_entry.ExpandToLimit(entry.offset)
- exp_entry = None
- if entry.expand_size:
- exp_entry = entry
- if exp_entry:
- exp_entry.ExpandToLimit(self._size)
-
- def CheckEntries(self):
- """Check that entries do not overlap or extend outside the section
-
- This also sorts entries, if needed and expands
- """
- if self._sort:
- self._SortEntries()
- self._ExpandEntries()
- offset = 0
- prev_name = 'None'
- for entry in self._entries.values():
- entry.CheckOffset()
- if (entry.offset < self._skip_at_start or
- entry.offset + entry.size > self._skip_at_start + self._size):
- entry.Raise("Offset %#x (%d) is outside the section starting "
- "at %#x (%d)" %
- (entry.offset, entry.offset, self._skip_at_start,
- self._skip_at_start))
- if entry.offset < offset:
- entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
- "ending at %#x (%d)" %
- (entry.offset, entry.offset, prev_name, offset, offset))
- offset = entry.offset + entry.size
- prev_name = entry.GetPath()
-
- def SetImagePos(self, image_pos):
- self._image_pos = image_pos
- for entry in self._entries.values():
- entry.SetImagePos(image_pos)
-
- def ProcessEntryContents(self):
- """Call the ProcessContents() method for each entry
-
- This is intended to adjust the contents as needed by the entry type.
- """
- for entry in self._entries.values():
- entry.ProcessContents()
-
- def WriteSymbols(self):
- """Write symbol values into binary files for access at run time"""
- for entry in self._entries.values():
- entry.WriteSymbols(self)
-
- def BuildSection(self, fd, base_offset):
- """Write the section to a file"""
- fd.seek(base_offset)
- fd.write(self.GetData())
-
- def GetData(self):
- """Get the contents of the section"""
- section_data = tools.GetBytes(self._pad_byte, self._size)
-
- for entry in self._entries.values():
- data = entry.GetData()
- base = self._pad_before + entry.offset - self._skip_at_start
- section_data = (section_data[:base] + data +
- section_data[base + len(data):])
- return section_data
-
- def LookupSymbol(self, sym_name, optional, msg):
- """Look up a symbol in an ELF file
-
- Looks up a symbol in an ELF file. Only entry types which come from an
- ELF image can be used by this function.
-
- At present the only entry property supported is offset.
-
- Args:
- sym_name: Symbol name in the ELF file to look up in the format
- _binman_<entry>_prop_<property> where <entry> is the name of
- the entry and <property> is the property to find (e.g.
- _binman_u_boot_prop_offset). As a special case, you can append
- _any to <entry> to have it search for any matching entry. E.g.
- _binman_u_boot_any_prop_offset will match entries called u-boot,
- u-boot-img and u-boot-nodtb)
- optional: True if the symbol is optional. If False this function
- will raise if the symbol is not found
- msg: Message to display if an error occurs
-
- Returns:
- Value that should be assigned to that symbol, or None if it was
- optional and not found
-
- Raises:
- ValueError if the symbol is invalid or not found, or references a
- property which is not supported
- """
- m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
- if not m:
- raise ValueError("%s: Symbol '%s' has invalid format" %
- (msg, sym_name))
- entry_name, prop_name = m.groups()
- entry_name = entry_name.replace('_', '-')
- entry = self._entries.get(entry_name)
- if not entry:
- if entry_name.endswith('-any'):
- root = entry_name[:-4]
- for name in self._entries:
- if name.startswith(root):
- rest = name[len(root):]
- if rest in ['', '-img', '-nodtb']:
- entry = self._entries[name]
- if not entry:
- err = ("%s: Entry '%s' not found in list (%s)" %
- (msg, entry_name, ','.join(self._entries.keys())))
- if optional:
- print('Warning: %s' % err, file=sys.stderr)
- return None
- raise ValueError(err)
- if prop_name == 'offset':
- return entry.offset
- elif prop_name == 'image_pos':
- return entry.image_pos
- else:
- raise ValueError("%s: No such property '%s'" % (msg, prop_name))
-
- def GetEntries(self):
- """Get the number of entries in a section
-
- Returns:
- Number of entries in a section
- """
- return self._entries
-
- def GetSize(self):
- """Get the size of a section in bytes
-
- This is only meaningful if the section has a pre-defined size, or the
- entries within it have been packed, so that the size has been
- calculated.
-
- Returns:
- Entry size in bytes
- """
- return self._size
-
- def WriteMap(self, fd, indent):
- """Write a map of the section to a .map file
-
- Args:
- fd: File to write the map to
- """
- Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
- self._size, self._image_pos)
- for entry in self._entries.values():
- entry.WriteMap(fd, indent + 1)
-
- def GetContentsByPhandle(self, phandle, source_entry):
- """Get the data contents of an entry specified by a phandle
-
- This uses a phandle to look up a node and and find the entry
- associated with it. Then it returnst he contents of that entry.
-
- Args:
- phandle: Phandle to look up (integer)
- source_entry: Entry containing that phandle (used for error
- reporting)
-
- Returns:
- data from associated entry (as a string), or None if not found
- """
- node = self._node.GetFdt().LookupPhandle(phandle)
- if not node:
- source_entry.Raise("Cannot find node for phandle %d" % phandle)
- for entry in self._entries.values():
- if entry._node == node:
- return entry.GetData()
- source_entry.Raise("Cannot find entry for node '%s'" % node.name)
-
- def ExpandSize(self, size):
- if size != self._size:
- self._size = size
-
- def GetRootSkipAtStart(self):
- if self._parent_section:
- return self._parent_section.GetRootSkipAtStart()
- return self._skip_at_start
-
- def GetImageSize(self):
- return self._image._size
diff --git a/tools/binman/cbfs_util.py b/tools/binman/cbfs_util.py
new file mode 100644
index 0000000..45e16da
--- /dev/null
+++ b/tools/binman/cbfs_util.py
@@ -0,0 +1,887 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2019 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+
+"""Support for coreboot's CBFS format
+
+CBFS supports a header followed by a number of files, generally targeted at SPI
+flash.
+
+The format is somewhat defined by documentation in the coreboot tree although
+it is necessary to rely on the C structures and source code (mostly cbfstool)
+to fully understand it.
+
+Currently supported: raw and stage types with compression, padding empty areas
+ with empty files, fixed-offset files
+"""
+
+from __future__ import print_function
+
+from collections import OrderedDict
+import io
+import struct
+import sys
+
+import command
+import elf
+import tools
+
+# Set to True to enable printing output while working
+DEBUG = False
+
+# Set to True to enable output from running cbfstool for debugging
+VERBOSE = False
+
+# The master header, at the start of the CBFS
+HEADER_FORMAT = '>IIIIIIII'
+HEADER_LEN = 0x20
+HEADER_MAGIC = 0x4f524243
+HEADER_VERSION1 = 0x31313131
+HEADER_VERSION2 = 0x31313132
+
+# The file header, at the start of each file in the CBFS
+FILE_HEADER_FORMAT = b'>8sIIII'
+FILE_HEADER_LEN = 0x18
+FILE_MAGIC = b'LARCHIVE'
+FILENAME_ALIGN = 16 # Filename lengths are aligned to this
+
+# A stage header containing information about 'stage' files
+# Yes this is correct: this header is in litte-endian format
+STAGE_FORMAT = '<IQQII'
+STAGE_LEN = 0x1c
+
+# An attribute describring the compression used in a file
+ATTR_COMPRESSION_FORMAT = '>IIII'
+ATTR_COMPRESSION_LEN = 0x10
+
+# Attribute tags
+# Depending on how the header was initialised, it may be backed with 0x00 or
+# 0xff. Support both.
+FILE_ATTR_TAG_UNUSED = 0
+FILE_ATTR_TAG_UNUSED2 = 0xffffffff
+FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
+FILE_ATTR_TAG_HASH = 0x68736148
+FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
+FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
+FILE_ATTR_TAG_PADDING = 0x47444150 # PDNG
+
+# This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
+# Not much more info is available, but we set it to 4, due to this comment in
+# cbfstool.c:
+# This causes 4 bytes to be left out at the end of the image, for two reasons:
+# 1. The cbfs master header pointer resides there
+# 2. Ssme cbfs implementations assume that an image that resides below 4GB has
+# a bootblock and get confused when the end of the image is at 4GB == 0.
+MIN_BOOTBLOCK_SIZE = 4
+
+# Files start aligned to this boundary in the CBFS
+ENTRY_ALIGN = 0x40
+
+# CBFSs must declare an architecture since much of the logic is designed with
+# x86 in mind. The effect of setting this value is not well documented, but in
+# general x86 is used and this makes use of a boot block and an image that ends
+# at the end of 32-bit address space.
+ARCHITECTURE_UNKNOWN = 0xffffffff
+ARCHITECTURE_X86 = 0x00000001
+ARCHITECTURE_ARM = 0x00000010
+ARCHITECTURE_AARCH64 = 0x0000aa64
+ARCHITECTURE_MIPS = 0x00000100
+ARCHITECTURE_RISCV = 0xc001d0de
+ARCHITECTURE_PPC64 = 0x407570ff
+
+ARCH_NAMES = {
+ ARCHITECTURE_UNKNOWN : 'unknown',
+ ARCHITECTURE_X86 : 'x86',
+ ARCHITECTURE_ARM : 'arm',
+ ARCHITECTURE_AARCH64 : 'arm64',
+ ARCHITECTURE_MIPS : 'mips',
+ ARCHITECTURE_RISCV : 'riscv',
+ ARCHITECTURE_PPC64 : 'ppc64',
+ }
+
+# File types. Only supported ones are included here
+TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
+TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
+TYPE_RAW = 0x50 # Raw file, possibly compressed
+TYPE_EMPTY = 0xffffffff # Empty data
+
+# Compression types
+COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
+
+COMPRESS_NAMES = {
+ COMPRESS_NONE : 'none',
+ COMPRESS_LZMA : 'lzma',
+ COMPRESS_LZ4 : 'lz4',
+ }
+
+def find_arch(find_name):
+ """Look up an architecture name
+
+ Args:
+ find_name: Architecture name to find
+
+ Returns:
+ ARCHITECTURE_... value or None if not found
+ """
+ for arch, name in ARCH_NAMES.items():
+ if name == find_name:
+ return arch
+ return None
+
+def find_compress(find_name):
+ """Look up a compression algorithm name
+
+ Args:
+ find_name: Compression algorithm name to find
+
+ Returns:
+ COMPRESS_... value or None if not found
+ """
+ for compress, name in COMPRESS_NAMES.items():
+ if name == find_name:
+ return compress
+ return None
+
+def compress_name(compress):
+ """Look up the name of a compression algorithm
+
+ Args:
+ compress: Compression algorithm number to find (COMPRESS_...)
+
+ Returns:
+ Compression algorithm name (string)
+
+ Raises:
+ KeyError if the algorithm number is invalid
+ """
+ return COMPRESS_NAMES[compress]
+
+def align_int(val, align):
+ """Align a value up to the given alignment
+
+ Args:
+ val: Integer value to align
+ align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
+
+ Returns:
+ integer value aligned to the required boundary, rounding up if necessary
+ """
+ return int((val + align - 1) / align) * align
+
+def align_int_down(val, align):
+ """Align a value down to the given alignment
+
+ Args:
+ val: Integer value to align
+ align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
+
+ Returns:
+ integer value aligned to the required boundary, rounding down if
+ necessary
+ """
+ return int(val / align) * align
+
+def _pack_string(instr):
+ """Pack a string to the required aligned size by adding padding
+
+ Args:
+ instr: String to process
+
+ Returns:
+ String with required padding (at least one 0x00 byte) at the end
+ """
+ val = tools.ToBytes(instr)
+ pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
+ return val + tools.GetBytes(0, pad_len - len(val))
+
+
+class CbfsFile(object):
+ """Class to represent a single CBFS file
+
+ This is used to hold the information about a file, including its contents.
+ Use the get_data_and_offset() method to obtain the raw output for writing to
+ CBFS.
+
+ Properties:
+ name: Name of file
+ offset: Offset of file data from start of file header
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
+ place this file anyway
+ data: Contents of file, uncompressed
+ data_len: Length of (possibly compressed) data in bytes
+ ftype: File type (TYPE_...)
+ compression: Compression type (COMPRESS_...)
+ memlen: Length of data in memory, i.e. the uncompressed length, None if
+ no compression algortihm is selected
+ load: Load address in memory if known, else None
+ entry: Entry address in memory if known, else None. This is where
+ execution starts after the file is loaded
+ base_address: Base address to use for 'stage' files
+ erase_byte: Erase byte to use for padding between the file header and
+ contents (used for empty files)
+ size: Size of the file in bytes (used for empty files)
+ """
+ def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
+ self.name = name
+ self.offset = None
+ self.cbfs_offset = cbfs_offset
+ self.data = data
+ self.ftype = ftype
+ self.compress = compress
+ self.memlen = None
+ self.load = None
+ self.entry = None
+ self.base_address = None
+ self.data_len = len(data)
+ self.erase_byte = None
+ self.size = None
+
+ def decompress(self):
+ """Handle decompressing data if necessary"""
+ indata = self.data
+ if self.compress == COMPRESS_LZ4:
+ data = tools.Decompress(indata, 'lz4')
+ elif self.compress == COMPRESS_LZMA:
+ data = tools.Decompress(indata, 'lzma')
+ else:
+ data = indata
+ self.memlen = len(data)
+ self.data = data
+ self.data_len = len(indata)
+
+ @classmethod
+ def stage(cls, base_address, name, data, cbfs_offset):
+ """Create a new stage file
+
+ Args:
+ base_address: Int base address for memory-mapping of ELF file
+ name: String file name to put in CBFS (does not need to correspond
+ to the name that the file originally came from)
+ data: Contents of file
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or
+ None to place this file anyway
+
+ Returns:
+ CbfsFile object containing the file information
+ """
+ cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
+ cfile.base_address = base_address
+ return cfile
+
+ @classmethod
+ def raw(cls, name, data, cbfs_offset, compress):
+ """Create a new raw file
+
+ Args:
+ name: String file name to put in CBFS (does not need to correspond
+ to the name that the file originally came from)
+ data: Contents of file
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or
+ None to place this file anyway
+ compress: Compression algorithm to use (COMPRESS_...)
+
+ Returns:
+ CbfsFile object containing the file information
+ """
+ return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
+
+ @classmethod
+ def empty(cls, space_to_use, erase_byte):
+ """Create a new empty file of a given size
+
+ Args:
+ space_to_use:: Size of available space, which must be at least as
+ large as the alignment size for this CBFS
+ erase_byte: Byte to use for contents of file (repeated through the
+ whole file)
+
+ Returns:
+ CbfsFile object containing the file information
+ """
+ cfile = CbfsFile('', TYPE_EMPTY, b'', None)
+ cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
+ cfile.erase_byte = erase_byte
+ return cfile
+
+ def calc_start_offset(self):
+ """Check if this file needs to start at a particular offset in CBFS
+
+ Returns:
+ None if the file can be placed anywhere, or
+ the largest offset where the file could start (integer)
+ """
+ if self.cbfs_offset is None:
+ return None
+ return self.cbfs_offset - self.get_header_len()
+
+ def get_header_len(self):
+ """Get the length of headers required for a file
+
+ This is the minimum length required before the actual data for this file
+ could start. It might start later if there is padding.
+
+ Returns:
+ Total length of all non-data fields, in bytes
+ """
+ name = _pack_string(self.name)
+ hdr_len = len(name) + FILE_HEADER_LEN
+ if self.ftype == TYPE_STAGE:
+ pass
+ elif self.ftype == TYPE_RAW:
+ hdr_len += ATTR_COMPRESSION_LEN
+ elif self.ftype == TYPE_EMPTY:
+ pass
+ else:
+ raise ValueError('Unknown file type %#x\n' % self.ftype)
+ return hdr_len
+
+ def get_data_and_offset(self, offset=None, pad_byte=None):
+ """Obtain the contents of the file, in CBFS format and the offset of
+ the data within the file
+
+ Returns:
+ tuple:
+ bytes representing the contents of this file, packed and aligned
+ for directly inserting into the final CBFS output
+ offset to the file data from the start of the returned data.
+ """
+ name = _pack_string(self.name)
+ hdr_len = len(name) + FILE_HEADER_LEN
+ attr_pos = 0
+ content = b''
+ attr = b''
+ pad = b''
+ data = self.data
+ if self.ftype == TYPE_STAGE:
+ elf_data = elf.DecodeElf(data, self.base_address)
+ content = struct.pack(STAGE_FORMAT, self.compress,
+ elf_data.entry, elf_data.load,
+ len(elf_data.data), elf_data.memsize)
+ data = elf_data.data
+ elif self.ftype == TYPE_RAW:
+ orig_data = data
+ if self.compress == COMPRESS_LZ4:
+ data = tools.Compress(orig_data, 'lz4')
+ elif self.compress == COMPRESS_LZMA:
+ data = tools.Compress(orig_data, 'lzma')
+ self.memlen = len(orig_data)
+ self.data_len = len(data)
+ attr = struct.pack(ATTR_COMPRESSION_FORMAT,
+ FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
+ self.compress, self.memlen)
+ elif self.ftype == TYPE_EMPTY:
+ data = tools.GetBytes(self.erase_byte, self.size)
+ else:
+ raise ValueError('Unknown type %#x when writing\n' % self.ftype)
+ if attr:
+ attr_pos = hdr_len
+ hdr_len += len(attr)
+ if self.cbfs_offset is not None:
+ pad_len = self.cbfs_offset - offset - hdr_len
+ if pad_len < 0: # pragma: no cover
+ # Test coverage of this is not available since this should never
+ # happen. It indicates that get_header_len() provided an
+ # incorrect value (too small) so that we decided that we could
+ # put this file at the requested place, but in fact a previous
+ # file extends far enough into the CBFS that this is not
+ # possible.
+ raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
+ (self.name, self.cbfs_offset, offset))
+ pad = tools.GetBytes(pad_byte, pad_len)
+ hdr_len += pad_len
+
+ # This is the offset of the start of the file's data,
+ size = len(content) + len(data)
+ hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, size,
+ self.ftype, attr_pos, hdr_len)
+
+ # Do a sanity check of the get_header_len() function, to ensure that it
+ # stays in lockstep with this function
+ expected_len = self.get_header_len()
+ actual_len = len(hdr + name + attr)
+ if expected_len != actual_len: # pragma: no cover
+ # Test coverage of this is not available since this should never
+ # happen. It probably indicates that get_header_len() is broken.
+ raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#d" %
+ (self.name, expected_len, actual_len))
+ return hdr + name + attr + pad + content + data, hdr_len
+
+
+class CbfsWriter(object):
+ """Class to handle writing a Coreboot File System (CBFS)
+
+ Usage is something like:
+
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
+ ...
+ data, cbfs_offset = cbw.get_data_and_offset()
+
+ Attributes:
+ _master_name: Name of the file containing the master header
+ _size: Size of the filesystem, in bytes
+ _files: Ordered list of files in the CBFS, each a CbfsFile
+ _arch: Architecture of the CBFS (ARCHITECTURE_...)
+ _bootblock_size: Size of the bootblock, typically at the end of the CBFS
+ _erase_byte: Byte to use for empty space in the CBFS
+ _align: Alignment to use for files, typically ENTRY_ALIGN
+ _base_address: Boot block offset in bytes from the start of CBFS.
+ Typically this is located at top of the CBFS. It is 0 when there is
+ no boot block
+ _header_offset: Offset of master header in bytes from start of CBFS
+ _contents_offset: Offset of first file header
+ _hdr_at_start: True if the master header is at the start of the CBFS,
+ instead of the end as normal for x86
+ _add_fileheader: True to add a fileheader around the master header
+ """
+ def __init__(self, size, arch=ARCHITECTURE_X86):
+ """Set up a new CBFS
+
+ This sets up all properties to default values. Files can be added using
+ add_file_raw(), etc.
+
+ Args:
+ size: Size of CBFS in bytes
+ arch: Architecture to declare for CBFS
+ """
+ self._master_name = 'cbfs master header'
+ self._size = size
+ self._files = OrderedDict()
+ self._arch = arch
+ self._bootblock_size = 0
+ self._erase_byte = 0xff
+ self._align = ENTRY_ALIGN
+ self._add_fileheader = False
+ if self._arch == ARCHITECTURE_X86:
+ # Allow 4 bytes for the header pointer. That holds the
+ # twos-compliment negative offset of the master header in bytes
+ # measured from one byte past the end of the CBFS
+ self._base_address = self._size - max(self._bootblock_size,
+ MIN_BOOTBLOCK_SIZE)
+ self._header_offset = self._base_address - HEADER_LEN
+ self._contents_offset = 0
+ self._hdr_at_start = False
+ else:
+ # For non-x86, different rules apply
+ self._base_address = 0
+ self._header_offset = align_int(self._base_address +
+ self._bootblock_size, 4)
+ self._contents_offset = align_int(self._header_offset +
+ FILE_HEADER_LEN +
+ self._bootblock_size, self._align)
+ self._hdr_at_start = True
+
+ def _skip_to(self, fd, offset):
+ """Write out pad bytes until a given offset
+
+ Args:
+ fd: File objext to write to
+ offset: Offset to write to
+ """
+ if fd.tell() > offset:
+ raise ValueError('No space for data before offset %#x (current offset %#x)' %
+ (offset, fd.tell()))
+ fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
+
+ def _pad_to(self, fd, offset):
+ """Write out pad bytes and/or an empty file until a given offset
+
+ Args:
+ fd: File objext to write to
+ offset: Offset to write to
+ """
+ self._align_to(fd, self._align)
+ upto = fd.tell()
+ if upto > offset:
+ raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
+ (offset, upto))
+ todo = align_int_down(offset - upto, self._align)
+ if todo:
+ cbf = CbfsFile.empty(todo, self._erase_byte)
+ fd.write(cbf.get_data_and_offset()[0])
+ self._skip_to(fd, offset)
+
+ def _align_to(self, fd, align):
+ """Write out pad bytes until a given alignment is reached
+
+ This only aligns if the resulting output would not reach the end of the
+ CBFS, since we want to leave the last 4 bytes for the master-header
+ pointer.
+
+ Args:
+ fd: File objext to write to
+ align: Alignment to require (e.g. 4 means pad to next 4-byte
+ boundary)
+ """
+ offset = align_int(fd.tell(), align)
+ if offset < self._size:
+ self._skip_to(fd, offset)
+
+ def add_file_stage(self, name, data, cbfs_offset=None):
+ """Add a new stage file to the CBFS
+
+ Args:
+ name: String file name to put in CBFS (does not need to correspond
+ to the name that the file originally came from)
+ data: Contents of file
+ cbfs_offset: Offset of this file's data within the CBFS, in bytes,
+ or None to place this file anywhere
+
+ Returns:
+ CbfsFile object created
+ """
+ cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
+ self._files[name] = cfile
+ return cfile
+
+ def add_file_raw(self, name, data, cbfs_offset=None,
+ compress=COMPRESS_NONE):
+ """Create a new raw file
+
+ Args:
+ name: String file name to put in CBFS (does not need to correspond
+ to the name that the file originally came from)
+ data: Contents of file
+ cbfs_offset: Offset of this file's data within the CBFS, in bytes,
+ or None to place this file anywhere
+ compress: Compression algorithm to use (COMPRESS_...)
+
+ Returns:
+ CbfsFile object created
+ """
+ cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
+ self._files[name] = cfile
+ return cfile
+
+ def _write_header(self, fd, add_fileheader):
+ """Write out the master header to a CBFS
+
+ Args:
+ fd: File object
+ add_fileheader: True to place the master header in a file header
+ record
+ """
+ if fd.tell() > self._header_offset:
+ raise ValueError('No space for header at offset %#x (current offset %#x)' %
+ (self._header_offset, fd.tell()))
+ if not add_fileheader:
+ self._pad_to(fd, self._header_offset)
+ hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
+ self._size, self._bootblock_size, self._align,
+ self._contents_offset, self._arch, 0xffffffff)
+ if add_fileheader:
+ name = _pack_string(self._master_name)
+ fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
+ TYPE_CBFSHEADER, 0,
+ FILE_HEADER_LEN + len(name)))
+ fd.write(name)
+ self._header_offset = fd.tell()
+ fd.write(hdr)
+ self._align_to(fd, self._align)
+ else:
+ fd.write(hdr)
+
+ def get_data(self):
+ """Obtain the full contents of the CBFS
+
+ Thhis builds the CBFS with headers and all required files.
+
+ Returns:
+ 'bytes' type containing the data
+ """
+ fd = io.BytesIO()
+
+ # THe header can go at the start in some cases
+ if self._hdr_at_start:
+ self._write_header(fd, add_fileheader=self._add_fileheader)
+ self._skip_to(fd, self._contents_offset)
+
+ # Write out each file
+ for cbf in self._files.values():
+ # Place the file at its requested place, if any
+ offset = cbf.calc_start_offset()
+ if offset is not None:
+ self._pad_to(fd, align_int_down(offset, self._align))
+ pos = fd.tell()
+ data, data_offset = cbf.get_data_and_offset(pos, self._erase_byte)
+ fd.write(data)
+ self._align_to(fd, self._align)
+ cbf.calced_cbfs_offset = pos + data_offset
+ if not self._hdr_at_start:
+ self._write_header(fd, add_fileheader=self._add_fileheader)
+
+ # Pad to the end and write a pointer to the CBFS master header
+ self._pad_to(fd, self._base_address or self._size - 4)
+ rel_offset = self._header_offset - self._size
+ fd.write(struct.pack('<I', rel_offset & 0xffffffff))
+
+ return fd.getvalue()
+
+
+class CbfsReader(object):
+ """Class to handle reading a Coreboot File System (CBFS)
+
+ Usage is something like:
+ cbfs = cbfs_util.CbfsReader(data)
+ cfile = cbfs.files['u-boot']
+ self.WriteFile('u-boot.bin', cfile.data)
+
+ Attributes:
+ files: Ordered list of CbfsFile objects
+ align: Alignment to use for files, typically ENTRT_ALIGN
+ stage_base_address: Base address to use when mapping ELF files into the
+ CBFS for TYPE_STAGE files. If this is larger than the code address
+ of the ELF file, then data at the start of the ELF file will not
+ appear in the CBFS. Currently there are no tests for behaviour as
+ documentation is sparse
+ magic: Integer magic number from master header (HEADER_MAGIC)
+ version: Version number of CBFS (HEADER_VERSION2)
+ rom_size: Size of CBFS
+ boot_block_size: Size of boot block
+ cbfs_offset: Offset of the first file in bytes from start of CBFS
+ arch: Architecture of CBFS file (ARCHITECTURE_...)
+ """
+ def __init__(self, data, read=True):
+ self.align = ENTRY_ALIGN
+ self.arch = None
+ self.boot_block_size = None
+ self.cbfs_offset = None
+ self.files = OrderedDict()
+ self.magic = None
+ self.rom_size = None
+ self.stage_base_address = 0
+ self.version = None
+ self.data = data
+ if read:
+ self.read()
+
+ def read(self):
+ """Read all the files in the CBFS and add them to self.files"""
+ with io.BytesIO(self.data) as fd:
+ # First, get the master header
+ if not self._find_and_read_header(fd, len(self.data)):
+ raise ValueError('Cannot find master header')
+ fd.seek(self.cbfs_offset)
+
+ # Now read in the files one at a time
+ while True:
+ cfile = self._read_next_file(fd)
+ if cfile:
+ self.files[cfile.name] = cfile
+ elif cfile is False:
+ break
+
+ def _find_and_read_header(self, fd, size):
+ """Find and read the master header in the CBFS
+
+ This looks at the pointer word at the very end of the CBFS. This is an
+ offset to the header relative to the size of the CBFS, which is assumed
+ to be known. Note that the offset is in *little endian* format.
+
+ Args:
+ fd: File to read from
+ size: Size of file
+
+ Returns:
+ True if header was found, False if not
+ """
+ orig_pos = fd.tell()
+ fd.seek(size - 4)
+ rel_offset, = struct.unpack('<I', fd.read(4))
+ pos = (size + rel_offset) & 0xffffffff
+ fd.seek(pos)
+ found = self._read_header(fd)
+ if not found:
+ print('Relative offset seems wrong, scanning whole image')
+ for pos in range(0, size - HEADER_LEN, 4):
+ fd.seek(pos)
+ found = self._read_header(fd)
+ if found:
+ break
+ fd.seek(orig_pos)
+ return found
+
+ def _read_next_file(self, fd):
+ """Read the next file from a CBFS
+
+ Args:
+ fd: File to read from
+
+ Returns:
+ CbfsFile object, if found
+ None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
+ False if at end of CBFS and reading should stop
+ """
+ file_pos = fd.tell()
+ data = fd.read(FILE_HEADER_LEN)
+ if len(data) < FILE_HEADER_LEN:
+ print('File header at %x ran out of data' % file_pos)
+ return False
+ magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
+ data)
+ if magic != FILE_MAGIC:
+ return False
+ pos = fd.tell()
+ name = self._read_string(fd)
+ if name is None:
+ print('String at %x ran out of data' % pos)
+ return False
+
+ if DEBUG:
+ print('name', name)
+
+ # If there are attribute headers present, read those
+ compress = self._read_attr(fd, file_pos, attr, offset)
+ if compress is None:
+ return False
+
+ # Create the correct CbfsFile object depending on the type
+ cfile = None
+ cbfs_offset = file_pos + offset
+ fd.seek(cbfs_offset, io.SEEK_SET)
+ if ftype == TYPE_CBFSHEADER:
+ self._read_header(fd)
+ elif ftype == TYPE_STAGE:
+ data = fd.read(STAGE_LEN)
+ cfile = CbfsFile.stage(self.stage_base_address, name, b'',
+ cbfs_offset)
+ (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
+ cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
+ cfile.data = fd.read(cfile.data_len)
+ elif ftype == TYPE_RAW:
+ data = fd.read(size)
+ cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
+ cfile.decompress()
+ if DEBUG:
+ print('data', data)
+ elif ftype == TYPE_EMPTY:
+ # Just read the data and discard it, since it is only padding
+ fd.read(size)
+ cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
+ else:
+ raise ValueError('Unknown type %#x when reading\n' % ftype)
+ if cfile:
+ cfile.offset = offset
+
+ # Move past the padding to the start of a possible next file. If we are
+ # already at an alignment boundary, then there is no padding.
+ pad = (self.align - fd.tell() % self.align) % self.align
+ fd.seek(pad, io.SEEK_CUR)
+ return cfile
+
+ @classmethod
+ def _read_attr(cls, fd, file_pos, attr, offset):
+ """Read attributes from the file
+
+ CBFS files can have attributes which are things that cannot fit into the
+ header. The only attributes currently supported are compression and the
+ unused tag.
+
+ Args:
+ fd: File to read from
+ file_pos: Position of file in fd
+ attr: Offset of attributes, 0 if none
+ offset: Offset of file data (used to indicate the end of the
+ attributes)
+
+ Returns:
+ Compression to use for the file (COMPRESS_...)
+ """
+ compress = COMPRESS_NONE
+ if not attr:
+ return compress
+ attr_size = offset - attr
+ fd.seek(file_pos + attr, io.SEEK_SET)
+ while attr_size:
+ pos = fd.tell()
+ hdr = fd.read(8)
+ if len(hdr) < 8:
+ print('Attribute tag at %x ran out of data' % pos)
+ return None
+ atag, alen = struct.unpack(">II", hdr)
+ data = hdr + fd.read(alen - 8)
+ if atag == FILE_ATTR_TAG_COMPRESSION:
+ # We don't currently use this information
+ atag, alen, compress, _decomp_size = struct.unpack(
+ ATTR_COMPRESSION_FORMAT, data)
+ elif atag == FILE_ATTR_TAG_UNUSED2:
+ break
+ else:
+ print('Unknown attribute tag %x' % atag)
+ attr_size -= len(data)
+ return compress
+
+ def _read_header(self, fd):
+ """Read the master header
+
+ Reads the header and stores the information obtained into the member
+ variables.
+
+ Args:
+ fd: File to read from
+
+ Returns:
+ True if header was read OK, False if it is truncated or has the
+ wrong magic or version
+ """
+ pos = fd.tell()
+ data = fd.read(HEADER_LEN)
+ if len(data) < HEADER_LEN:
+ print('Header at %x ran out of data' % pos)
+ return False
+ (self.magic, self.version, self.rom_size, self.boot_block_size,
+ self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
+ HEADER_FORMAT, data)
+ return self.magic == HEADER_MAGIC and (
+ self.version == HEADER_VERSION1 or
+ self.version == HEADER_VERSION2)
+
+ @classmethod
+ def _read_string(cls, fd):
+ """Read a string from a file
+
+ This reads a string and aligns the data to the next alignment boundary
+
+ Args:
+ fd: File to read from
+
+ Returns:
+ string read ('str' type) encoded to UTF-8, or None if we ran out of
+ data
+ """
+ val = b''
+ while True:
+ data = fd.read(FILENAME_ALIGN)
+ if len(data) < FILENAME_ALIGN:
+ return None
+ pos = data.find(b'\0')
+ if pos == -1:
+ val += data
+ else:
+ val += data[:pos]
+ break
+ return val.decode('utf-8')
+
+
+def cbfstool(fname, *cbfs_args, **kwargs):
+ """Run cbfstool with provided arguments
+
+ If the tool fails then this function raises an exception and prints out the
+ output and stderr.
+
+ Args:
+ fname: Filename of CBFS
+ *cbfs_args: List of arguments to pass to cbfstool
+
+ Returns:
+ CommandResult object containing the results
+ """
+ args = ['cbfstool', fname] + list(cbfs_args)
+ if kwargs.get('base') is not None:
+ args += ['-b', '%#x' % kwargs['base']]
+ result = command.RunPipe([args], capture=not VERBOSE,
+ capture_stderr=not VERBOSE, raise_on_error=False)
+ if result.return_code:
+ print(result.stderr, file=sys.stderr)
+ raise Exception("Failed to run (error %d): '%s'" %
+ (result.return_code, ' '.join(args)))
diff --git a/tools/binman/cbfs_util_test.py b/tools/binman/cbfs_util_test.py
new file mode 100755
index 0000000..0fe4aa4
--- /dev/null
+++ b/tools/binman/cbfs_util_test.py
@@ -0,0 +1,625 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2019 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+
+"""Tests for cbfs_util
+
+These create and read various CBFSs and compare the results with expected
+values and with cbfstool
+"""
+
+from __future__ import print_function
+
+import io
+import os
+import shutil
+import struct
+import tempfile
+import unittest
+
+import cbfs_util
+from cbfs_util import CbfsWriter
+import elf
+import test_util
+import tools
+
+U_BOOT_DATA = b'1234'
+U_BOOT_DTB_DATA = b'udtb'
+COMPRESS_DATA = b'compress xxxxxxxxxxxxxxxxxxxxxx data'
+
+
+class TestCbfs(unittest.TestCase):
+ """Test of cbfs_util classes"""
+ #pylint: disable=W0212
+ @classmethod
+ def setUpClass(cls):
+ # Create a temporary directory for test files
+ cls._indir = tempfile.mkdtemp(prefix='cbfs_util.')
+ tools.SetInputDirs([cls._indir])
+
+ # Set up some useful data files
+ TestCbfs._make_input_file('u-boot.bin', U_BOOT_DATA)
+ TestCbfs._make_input_file('u-boot.dtb', U_BOOT_DTB_DATA)
+ TestCbfs._make_input_file('compress', COMPRESS_DATA)
+
+ # Set up a temporary output directory, used by the tools library when
+ # compressing files
+ tools.PrepareOutputDir(None)
+
+ cls.have_cbfstool = True
+ try:
+ tools.Run('which', 'cbfstool')
+ except:
+ cls.have_cbfstool = False
+
+ cls.have_lz4 = True
+ try:
+ tools.Run('lz4', '--no-frame-crc', '-c',
+ tools.GetInputFilename('u-boot.bin'))
+ except:
+ cls.have_lz4 = False
+
+ @classmethod
+ def tearDownClass(cls):
+ """Remove the temporary input directory and its contents"""
+ if cls._indir:
+ shutil.rmtree(cls._indir)
+ cls._indir = None
+ tools.FinaliseOutputDir()
+
+ @classmethod
+ def _make_input_file(cls, fname, contents):
+ """Create a new test input file, creating directories as needed
+
+ Args:
+ fname: Filename to create
+ contents: File contents to write in to the file
+ Returns:
+ Full pathname of file created
+ """
+ pathname = os.path.join(cls._indir, fname)
+ tools.WriteFile(pathname, contents)
+ return pathname
+
+ def _check_hdr(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86):
+ """Check that the CBFS has the expected header
+
+ Args:
+ data: Data to check
+ size: Expected ROM size
+ offset: Expected offset to first CBFS file
+ arch: Expected architecture
+
+ Returns:
+ CbfsReader object containing the CBFS
+ """
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(cbfs_util.HEADER_MAGIC, cbfs.magic)
+ self.assertEqual(cbfs_util.HEADER_VERSION2, cbfs.version)
+ self.assertEqual(size, cbfs.rom_size)
+ self.assertEqual(0, cbfs.boot_block_size)
+ self.assertEqual(cbfs_util.ENTRY_ALIGN, cbfs.align)
+ self.assertEqual(offset, cbfs.cbfs_offset)
+ self.assertEqual(arch, cbfs.arch)
+ return cbfs
+
+ def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x38,
+ data=U_BOOT_DATA, cbfs_offset=None):
+ """Check that the U-Boot file is as expected
+
+ Args:
+ cbfs: CbfsReader object to check
+ ftype: Expected file type
+ offset: Expected offset of file
+ data: Expected data in file
+ cbfs_offset: Expected CBFS offset for file's data
+
+ Returns:
+ CbfsFile object containing the file
+ """
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual('u-boot', cfile.name)
+ self.assertEqual(offset, cfile.offset)
+ if cbfs_offset is not None:
+ self.assertEqual(cbfs_offset, cfile.cbfs_offset)
+ self.assertEqual(data, cfile.data)
+ self.assertEqual(ftype, cfile.ftype)
+ self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
+ self.assertEqual(len(data), cfile.memlen)
+ return cfile
+
+ def _check_dtb(self, cbfs, offset=0x38, data=U_BOOT_DTB_DATA,
+ cbfs_offset=None):
+ """Check that the U-Boot dtb file is as expected
+
+ Args:
+ cbfs: CbfsReader object to check
+ offset: Expected offset of file
+ data: Expected data in file
+ cbfs_offset: Expected CBFS offset for file's data
+ """
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile = cbfs.files['u-boot-dtb']
+ self.assertEqual('u-boot-dtb', cfile.name)
+ self.assertEqual(offset, cfile.offset)
+ if cbfs_offset is not None:
+ self.assertEqual(cbfs_offset, cfile.cbfs_offset)
+ self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
+ self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype)
+ self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
+ self.assertEqual(len(U_BOOT_DTB_DATA), cfile.memlen)
+
+ def _check_raw(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86):
+ """Check that two raw files are added as expected
+
+ Args:
+ data: Data to check
+ size: Expected ROM size
+ offset: Expected offset to first CBFS file
+ arch: Expected architecture
+ """
+ cbfs = self._check_hdr(data, size, offset=offset, arch=arch)
+ self._check_uboot(cbfs)
+ self._check_dtb(cbfs)
+
+ def _get_expected_cbfs(self, size, arch='x86', compress=None, base=None):
+ """Get the file created by cbfstool for a particular scenario
+
+ Args:
+ size: Size of the CBFS in bytes
+ arch: Architecture of the CBFS, as a string
+ compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA
+ base: Base address of file, or None to put it anywhere
+
+ Returns:
+ Resulting CBFS file, or None if cbfstool is not available
+ """
+ if not self.have_cbfstool or not self.have_lz4:
+ return None
+ cbfs_fname = os.path.join(self._indir, 'test.cbfs')
+ cbfs_util.cbfstool(cbfs_fname, 'create', '-m', arch, '-s', '%#x' % size)
+ if base:
+ base = [(1 << 32) - size + b for b in base]
+ cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot', '-t', 'raw',
+ '-c', compress and compress[0] or 'none',
+ '-f', tools.GetInputFilename(
+ compress and 'compress' or 'u-boot.bin'),
+ base=base[0] if base else None)
+ cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot-dtb', '-t', 'raw',
+ '-c', compress and compress[1] or 'none',
+ '-f', tools.GetInputFilename(
+ compress and 'compress' or 'u-boot.dtb'),
+ base=base[1] if base else None)
+ return cbfs_fname
+
+ def _compare_expected_cbfs(self, data, cbfstool_fname):
+ """Compare against what cbfstool creates
+
+ This compares what binman creates with what cbfstool creates for what
+ is proportedly the same thing.
+
+ Args:
+ data: CBFS created by binman
+ cbfstool_fname: CBFS created by cbfstool
+ """
+ if not self.have_cbfstool or not self.have_lz4:
+ return
+ expect = tools.ReadFile(cbfstool_fname)
+ if expect != data:
+ tools.WriteFile('/tmp/expect', expect)
+ tools.WriteFile('/tmp/actual', data)
+ print('diff -y <(xxd -g1 /tmp/expect) <(xxd -g1 /tmp/actual) | colordiff')
+ self.fail('cbfstool produced a different result')
+
+ def test_cbfs_functions(self):
+ """Test global functions of cbfs_util"""
+ self.assertEqual(cbfs_util.ARCHITECTURE_X86, cbfs_util.find_arch('x86'))
+ self.assertIsNone(cbfs_util.find_arch('bad-arch'))
+
+ self.assertEqual(cbfs_util.COMPRESS_LZMA, cbfs_util.find_compress('lzma'))
+ self.assertIsNone(cbfs_util.find_compress('bad-comp'))
+
+ def test_cbfstool_failure(self):
+ """Test failure to run cbfstool"""
+ if not self.have_cbfstool:
+ self.skipTest('No cbfstool available')
+ try:
+ # In verbose mode this test fails since stderr is not captured. Fix
+ # this by turning off verbosity.
+ old_verbose = cbfs_util.VERBOSE
+ cbfs_util.VERBOSE = False
+ with test_util.capture_sys_output() as (_stdout, stderr):
+ with self.assertRaises(Exception) as e:
+ cbfs_util.cbfstool('missing-file', 'bad-command')
+ finally:
+ cbfs_util.VERBOSE = old_verbose
+ self.assertIn('Unknown command', stderr.getvalue())
+ self.assertIn('Failed to run', str(e.exception))
+
+ def test_cbfs_raw(self):
+ """Test base handling of a Coreboot Filesystem (CBFS)"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA)
+ data = cbw.get_data()
+ self._check_raw(data, size)
+ cbfs_fname = self._get_expected_cbfs(size=size)
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_invalid_file_type(self):
+ """Check handling of an invalid file type when outputiing a CBFS"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA)
+
+ # Change the type manually before generating the CBFS, and make sure
+ # that the generator complains
+ cfile.ftype = 0xff
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('Unknown type 0xff when writing', str(e.exception))
+
+ def test_cbfs_invalid_file_type_on_read(self):
+ """Check handling of an invalid file type when reading the CBFS"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+
+ data = cbw.get_data()
+
+ # Read in the first file header
+ cbr = cbfs_util.CbfsReader(data, read=False)
+ with io.BytesIO(data) as fd:
+ self.assertTrue(cbr._find_and_read_header(fd, len(data)))
+ pos = fd.tell()
+ hdr_data = fd.read(cbfs_util.FILE_HEADER_LEN)
+ magic, size, ftype, attr, offset = struct.unpack(
+ cbfs_util.FILE_HEADER_FORMAT, hdr_data)
+
+ # Create a new CBFS with a change to the file type
+ ftype = 0xff
+ newdata = data[:pos]
+ newdata += struct.pack(cbfs_util.FILE_HEADER_FORMAT, magic, size, ftype,
+ attr, offset)
+ newdata += data[pos + cbfs_util.FILE_HEADER_LEN:]
+
+ # Read in this CBFS and make sure that the reader complains
+ with self.assertRaises(ValueError) as e:
+ cbfs_util.CbfsReader(newdata)
+ self.assertIn('Unknown type 0xff when reading', str(e.exception))
+
+ def test_cbfs_no_space(self):
+ """Check handling of running out of space in the CBFS"""
+ size = 0x60
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('No space for header', str(e.exception))
+
+ def test_cbfs_no_space_skip(self):
+ """Check handling of running out of space in CBFS with file header"""
+ size = 0x5c
+ cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64)
+ cbw._add_fileheader = True
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('No space for data before offset', str(e.exception))
+
+ def test_cbfs_no_space_pad(self):
+ """Check handling of running out of space in CBFS with file header"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw._add_fileheader = True
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('No space for data before pad offset', str(e.exception))
+
+ def test_cbfs_bad_header_ptr(self):
+ """Check handling of a bad master-header pointer"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ # Add one to the pointer to make it invalid
+ newdata = data[:-4] + struct.pack('<I', cbw._header_offset + 1)
+
+ # We should still be able to find the master header by searching
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ cbfs = cbfs_util.CbfsReader(newdata)
+ self.assertIn('Relative offset seems wrong', stdout.getvalue())
+ self.assertIn('u-boot', cbfs.files)
+ self.assertEqual(size, cbfs.rom_size)
+
+ def test_cbfs_bad_header(self):
+ """Check handling of a bad master header"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ # Drop most of the header and try reading the modified CBFS
+ newdata = data[:cbw._header_offset + 4]
+
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ with self.assertRaises(ValueError) as e:
+ cbfs_util.CbfsReader(newdata)
+ self.assertIn('Relative offset seems wrong', stdout.getvalue())
+ self.assertIn('Cannot find master header', str(e.exception))
+
+ def test_cbfs_bad_file_header(self):
+ """Check handling of a bad file header"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ # Read in the CBFS master header (only), then stop
+ cbr = cbfs_util.CbfsReader(data, read=False)
+ with io.BytesIO(data) as fd:
+ self.assertTrue(cbr._find_and_read_header(fd, len(data)))
+ pos = fd.tell()
+
+ # Remove all but 4 bytes of the file headerm and try to read the file
+ newdata = data[:pos + 4]
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ with io.BytesIO(newdata) as fd:
+ fd.seek(pos)
+ self.assertEqual(False, cbr._read_next_file(fd))
+ self.assertIn('File header at 0 ran out of data', stdout.getvalue())
+
+ def test_cbfs_bad_file_string(self):
+ """Check handling of an incomplete filename string"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('16-characters xx', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ # Read in the CBFS master header (only), then stop
+ cbr = cbfs_util.CbfsReader(data, read=False)
+ with io.BytesIO(data) as fd:
+ self.assertTrue(cbr._find_and_read_header(fd, len(data)))
+ pos = fd.tell()
+
+ # Create a new CBFS with only the first 16 bytes of the file name, then
+ # try to read the file
+ newdata = data[:pos + cbfs_util.FILE_HEADER_LEN + 16]
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ with io.BytesIO(newdata) as fd:
+ fd.seek(pos)
+ self.assertEqual(False, cbr._read_next_file(fd))
+ self.assertIn('String at %x ran out of data' %
+ cbfs_util.FILE_HEADER_LEN, stdout.getvalue())
+
+ def test_cbfs_debug(self):
+ """Check debug output"""
+ size = 0x70
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ try:
+ cbfs_util.DEBUG = True
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ cbfs_util.CbfsReader(data)
+ self.assertEqual('name u-boot\ndata %s\n' % U_BOOT_DATA,
+ stdout.getvalue())
+ finally:
+ cbfs_util.DEBUG = False
+
+ def test_cbfs_bad_attribute(self):
+ """Check handling of bad attribute tag"""
+ if not self.have_lz4:
+ self.skipTest('lz4 --no-frame-crc not available')
+ size = 0x140
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
+ compress=cbfs_util.COMPRESS_LZ4)
+ data = cbw.get_data()
+
+ # Search the CBFS for the expected compression tag
+ with io.BytesIO(data) as fd:
+ while True:
+ pos = fd.tell()
+ tag, = struct.unpack('>I', fd.read(4))
+ if tag == cbfs_util.FILE_ATTR_TAG_COMPRESSION:
+ break
+
+ # Create a new CBFS with the tag changed to something invalid
+ newdata = data[:pos] + struct.pack('>I', 0x123) + data[pos + 4:]
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ cbfs_util.CbfsReader(newdata)
+ self.assertEqual('Unknown attribute tag 123\n', stdout.getvalue())
+
+ def test_cbfs_missing_attribute(self):
+ """Check handling of an incomplete attribute tag"""
+ if not self.have_lz4:
+ self.skipTest('lz4 --no-frame-crc not available')
+ size = 0x140
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
+ compress=cbfs_util.COMPRESS_LZ4)
+ data = cbw.get_data()
+
+ # Read in the CBFS master header (only), then stop
+ cbr = cbfs_util.CbfsReader(data, read=False)
+ with io.BytesIO(data) as fd:
+ self.assertTrue(cbr._find_and_read_header(fd, len(data)))
+ pos = fd.tell()
+
+ # Create a new CBFS with only the first 4 bytes of the compression tag,
+ # then try to read the file
+ tag_pos = pos + cbfs_util.FILE_HEADER_LEN + cbfs_util.FILENAME_ALIGN
+ newdata = data[:tag_pos + 4]
+ with test_util.capture_sys_output() as (stdout, _stderr):
+ with io.BytesIO(newdata) as fd:
+ fd.seek(pos)
+ self.assertEqual(False, cbr._read_next_file(fd))
+ self.assertIn('Attribute tag at %x ran out of data' % tag_pos,
+ stdout.getvalue())
+
+ def test_cbfs_file_master_header(self):
+ """Check handling of a file containing a master header"""
+ size = 0x100
+ cbw = CbfsWriter(size)
+ cbw._add_fileheader = True
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ data = cbw.get_data()
+
+ cbr = cbfs_util.CbfsReader(data)
+ self.assertIn('u-boot', cbr.files)
+ self.assertEqual(size, cbr.rom_size)
+
+ def test_cbfs_arch(self):
+ """Test on non-x86 architecture"""
+ size = 0x100
+ cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA)
+ data = cbw.get_data()
+ self._check_raw(data, size, offset=0x40,
+ arch=cbfs_util.ARCHITECTURE_PPC64)
+
+ # Compare against what cbfstool creates
+ cbfs_fname = self._get_expected_cbfs(size=size, arch='ppc64')
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_stage(self):
+ """Tests handling of a Coreboot Filesystem (CBFS)"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+ elf_fname = os.path.join(self._indir, 'cbfs-stage.elf')
+ elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA)
+
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cbw.add_file_stage('u-boot', tools.ReadFile(elf_fname))
+
+ data = cbw.get_data()
+ cbfs = self._check_hdr(data, size)
+ load = 0xfef20000
+ entry = load + 2
+
+ cfile = self._check_uboot(cbfs, cbfs_util.TYPE_STAGE, offset=0x28,
+ data=U_BOOT_DATA + U_BOOT_DTB_DATA)
+
+ self.assertEqual(entry, cfile.entry)
+ self.assertEqual(load, cfile.load)
+ self.assertEqual(len(U_BOOT_DATA) + len(U_BOOT_DTB_DATA),
+ cfile.data_len)
+
+ # Compare against what cbfstool creates
+ if self.have_cbfstool:
+ cbfs_fname = os.path.join(self._indir, 'test.cbfs')
+ cbfs_util.cbfstool(cbfs_fname, 'create', '-m', 'x86', '-s',
+ '%#x' % size)
+ cbfs_util.cbfstool(cbfs_fname, 'add-stage', '-n', 'u-boot',
+ '-f', elf_fname)
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_raw_compress(self):
+ """Test base handling of compressing raw files"""
+ if not self.have_lz4:
+ self.skipTest('lz4 --no-frame-crc not available')
+ size = 0x140
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
+ compress=cbfs_util.COMPRESS_LZ4)
+ cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA, None,
+ compress=cbfs_util.COMPRESS_LZMA)
+ data = cbw.get_data()
+
+ cbfs = self._check_hdr(data, size)
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual(cfile.name, 'u-boot')
+ self.assertEqual(cfile.offset, 56)
+ self.assertEqual(cfile.data, COMPRESS_DATA)
+ self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW)
+ self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZ4)
+ self.assertEqual(cfile.memlen, len(COMPRESS_DATA))
+
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile = cbfs.files['u-boot-dtb']
+ self.assertEqual(cfile.name, 'u-boot-dtb')
+ self.assertEqual(cfile.offset, 56)
+ self.assertEqual(cfile.data, COMPRESS_DATA)
+ self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW)
+ self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZMA)
+ self.assertEqual(cfile.memlen, len(COMPRESS_DATA))
+
+ cbfs_fname = self._get_expected_cbfs(size=size, compress=['lz4', 'lzma'])
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_raw_space(self):
+ """Test files with unused space in the CBFS"""
+ size = 0xf0
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA)
+ data = cbw.get_data()
+ self._check_raw(data, size)
+ cbfs_fname = self._get_expected_cbfs(size=size)
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_offset(self):
+ """Test a CBFS with files at particular offsets"""
+ size = 0x200
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x140)
+
+ data = cbw.get_data()
+ cbfs = self._check_hdr(data, size)
+ self._check_uboot(cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x40,
+ cbfs_offset=0x40)
+ self._check_dtb(cbfs, offset=0x40, cbfs_offset=0x140)
+
+ cbfs_fname = self._get_expected_cbfs(size=size, base=(0x40, 0x140))
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_invalid_file_type_header(self):
+ """Check handling of an invalid file type when outputting a header"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA, 0)
+
+ # Change the type manually before generating the CBFS, and make sure
+ # that the generator complains
+ cfile.ftype = 0xff
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('Unknown file type 0xff', str(e.exception))
+
+ def test_cbfs_offset_conflict(self):
+ """Test a CBFS with files that want to overlap"""
+ size = 0x200
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x80)
+
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('No space for data before pad offset', str(e.exception))
+
+ def test_cbfs_check_offset(self):
+ """Test that we can discover the offset of a file after writing it"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA)
+ data = cbw.get_data()
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(0x38, cbfs.files['u-boot'].cbfs_offset)
+ self.assertEqual(0x78, cbfs.files['u-boot-dtb'].cbfs_offset)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py
index 3886d52..a43aec6 100644
--- a/tools/binman/cmdline.py
+++ b/tools/binman/cmdline.py
@@ -5,7 +5,7 @@
# Command-line parser for binman
#
-from optparse import OptionParser
+from argparse import ArgumentParser
def ParseArgs(argv):
"""Parse the binman command-line arguments
@@ -17,50 +17,83 @@
options provides access to the options (e.g. option.debug)
args is a list of string arguments
"""
- parser = OptionParser()
- parser.add_option('-a', '--entry-arg', type='string', action='append',
+ if '-H' in argv:
+ argv.append('build')
+
+ epilog = '''Binman creates and manipulate images for a board from a set of binaries. Binman is
+controlled by a description in the board device tree.'''
+
+ parser = ArgumentParser(epilog=epilog)
+ parser.add_argument('-B', '--build-dir', type=str, default='b',
+ help='Directory containing the build output')
+ parser.add_argument('-D', '--debug', action='store_true',
+ help='Enabling debugging (provides a full traceback on error)')
+ parser.add_argument('-H', '--full-help', action='store_true',
+ default=False, help='Display the README file')
+ parser.add_argument('--toolpath', type=str, action='append',
+ help='Add a path to the directories containing tools')
+ parser.add_argument('-v', '--verbosity', default=1,
+ type=int, help='Control verbosity: 0=silent, 1=warnings, 2=notices, '
+ '3=info, 4=detail, 5=debug')
+
+ subparsers = parser.add_subparsers(dest='cmd')
+
+ build_parser = subparsers.add_parser('build', help='Build firmware image')
+ build_parser.add_argument('-a', '--entry-arg', type=str, action='append',
help='Set argument value arg=value')
- parser.add_option('-b', '--board', type='string',
+ build_parser.add_argument('-b', '--board', type=str,
help='Board name to build')
- parser.add_option('-B', '--build-dir', type='string', default='b',
- help='Directory containing the build output')
- parser.add_option('-d', '--dt', type='string',
+ build_parser.add_argument('-d', '--dt', type=str,
help='Configuration file (.dtb) to use')
- parser.add_option('-D', '--debug', action='store_true',
- help='Enabling debugging (provides a full traceback on error)')
- parser.add_option('-E', '--entry-docs', action='store_true',
- help='Write out entry documentation (see README.entries)')
- parser.add_option('--fake-dtb', action='store_true',
+ build_parser.add_argument('--fake-dtb', action='store_true',
help='Use fake device tree contents (for testing only)')
- parser.add_option('-i', '--image', type='string', action='append',
+ build_parser.add_argument('-i', '--image', type=str, action='append',
help='Image filename to build (if not specified, build all)')
- parser.add_option('-I', '--indir', action='append',
- help='Add a path to a directory to use for input files')
- parser.add_option('-H', '--full-help', action='store_true',
- default=False, help='Display the README file')
- parser.add_option('-m', '--map', action='store_true',
+ build_parser.add_argument('-I', '--indir', action='append',
+ help='Add a path to the list of directories to use for input files')
+ build_parser.add_argument('-m', '--map', action='store_true',
default=False, help='Output a map file for each image')
- parser.add_option('-O', '--outdir', type='string',
+ build_parser.add_argument('-O', '--outdir', type=str,
action='store', help='Path to directory to use for intermediate and '
'output files')
- parser.add_option('-p', '--preserve', action='store_true',\
+ build_parser.add_argument('-p', '--preserve', action='store_true',\
help='Preserve temporary output directory even if option -O is not '
'given')
- parser.add_option('-P', '--processes', type=int,
- help='set number of processes to use for running tests')
- parser.add_option('-t', '--test', action='store_true',
- default=False, help='run tests')
- parser.add_option('-T', '--test-coverage', action='store_true',
- default=False, help='run tests and check for 100% coverage')
- parser.add_option('-u', '--update-fdt', action='store_true',
+ build_parser.add_argument('-u', '--update-fdt', action='store_true',
default=False, help='Update the binman node with offset/size info')
- parser.add_option('-v', '--verbosity', default=1,
- type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
- '4=debug')
- parser.usage += """
+ entry_parser = subparsers.add_parser('entry-docs',
+ help='Write out entry documentation (see README.entries)')
+
+ list_parser = subparsers.add_parser('ls', help='List files in an image')
+ list_parser.add_argument('-i', '--image', type=str, required=True,
+ help='Image filename to list')
+ list_parser.add_argument('paths', type=str, nargs='*',
+ help='Paths within file to list (wildcard)')
+
+ extract_parser = subparsers.add_parser('extract',
+ help='Extract files from an image')
+ extract_parser.add_argument('-i', '--image', type=str, required=True,
+ help='Image filename to extract')
+ extract_parser.add_argument('-f', '--filename', type=str,
+ help='Output filename to write to')
+ extract_parser.add_argument('-O', '--outdir', type=str, default='',
+ help='Path to directory to use for output files')
+ extract_parser.add_argument('paths', type=str, nargs='*',
+ help='Paths within file to extract (wildcard)')
+ extract_parser.add_argument('-U', '--uncompressed', action='store_true',
+ help='Output raw uncompressed data for compressed entries')
-Create images for a board from a set of binaries. It is controlled by a
-description in the board device tree."""
+ test_parser = subparsers.add_parser('test', help='Run tests')
+ test_parser.add_argument('-P', '--processes', type=int,
+ help='set number of processes to use for running tests')
+ test_parser.add_argument('-T', '--test-coverage', action='store_true',
+ default=False, help='run tests and check for 100%% coverage')
+ test_parser.add_argument('-X', '--test-preserve-dirs', action='store_true',
+ help='Preserve and display test-created input directories; also '
+ 'preserve the output directory if a single test is run (pass test '
+ 'name at the end of the command line')
+ test_parser.add_argument('tests', nargs='*',
+ help='Test names to run (omit for all)')
return parser.parse_args(argv)
diff --git a/tools/binman/control.py b/tools/binman/control.py
index 20186ee..dc898be 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -12,6 +12,7 @@
import sys
import tools
+import cbfs_util
import command
import elf
from image import Image
@@ -66,19 +67,120 @@
from entry import Entry
Entry.WriteDocs(modules, test_missing)
-def Binman(options, args):
+
+def ListEntries(image_fname, entry_paths):
+ """List the entries in an image
+
+ This decodes the supplied image and displays a table of entries from that
+ image, preceded by a header.
+
+ Args:
+ image_fname: Image filename to process
+ entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
+ 'section/u-boot'])
+ """
+ image = Image.FromFile(image_fname)
+
+ entries, lines, widths = image.GetListEntries(entry_paths)
+
+ num_columns = len(widths)
+ for linenum, line in enumerate(lines):
+ if linenum == 1:
+ # Print header line
+ print('-' * (sum(widths) + num_columns * 2))
+ out = ''
+ for i, item in enumerate(line):
+ width = -widths[i]
+ if item.startswith('>'):
+ width = -width
+ item = item[1:]
+ txt = '%*s ' % (width, item)
+ out += txt
+ print(out.rstrip())
+
+
+def ReadEntry(image_fname, entry_path, decomp=True):
+ """Extract an entry from an image
+
+ This extracts the data from a particular entry in an image
+
+ Args:
+ image_fname: Image filename to process
+ entry_path: Path to entry to extract
+ decomp: True to return uncompressed data, if the data is compress
+ False to return the raw data
+
+ Returns:
+ data extracted from the entry
+ """
+ image = Image.FromFile(image_fname)
+ entry = image.FindEntryPath(entry_path)
+ return entry.ReadData(decomp)
+
+
+def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
+ decomp=True):
+ """Extract the data from one or more entries and write it to files
+
+ Args:
+ image_fname: Image filename to process
+ output_fname: Single output filename to use if extracting one file, None
+ otherwise
+ outdir: Output directory to use (for any number of files), else None
+ entry_paths: List of entry paths to extract
+ decomp: True to compress the entry data
+
+ Returns:
+ List of EntryInfo records that were written
+ """
+ image = Image.FromFile(image_fname)
+
+ # Output an entry to a single file, as a special case
+ if output_fname:
+ if not entry_paths:
+ raise ValueError('Must specify an entry path to write with -o')
+ if len(entry_paths) != 1:
+ raise ValueError('Must specify exactly one entry path to write with -o')
+ entry = image.FindEntryPath(entry_paths[0])
+ data = entry.ReadData(decomp)
+ tools.WriteFile(output_fname, data)
+ tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
+ return
+
+ # Otherwise we will output to a path given by the entry path of each entry.
+ # This means that entries will appear in subdirectories if they are part of
+ # a sub-section.
+ einfos = image.GetListEntries(entry_paths)[0]
+ tout.Notice('%d entries match and will be written' % len(einfos))
+ for einfo in einfos:
+ entry = einfo.entry
+ data = entry.ReadData(decomp)
+ path = entry.GetPath()[1:]
+ fname = os.path.join(outdir, path)
+
+ # If this entry has children, create a directory for it and put its
+ # data in a file called 'root' in that directory
+ if entry.GetEntries():
+ if not os.path.exists(fname):
+ os.makedirs(fname)
+ fname = os.path.join(fname, 'root')
+ tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
+ tools.WriteFile(fname, data)
+ return einfos
+
+
+def Binman(args):
"""The main control code for binman
This assumes that help and test options have already been dealt with. It
deals with the core task of building images.
Args:
- options: Command line options object
- args: Command line arguments (list of strings)
+ args: Command line arguments Namespace object
"""
global images
- if options.full_help:
+ if args.full_help:
pager = os.getenv('PAGER')
if not pager:
pager = 'more'
@@ -87,18 +189,31 @@
command.Run(pager, fname)
return 0
+ if args.cmd == 'ls':
+ ListEntries(args.image, args.paths)
+ return 0
+
+ if args.cmd == 'extract':
+ try:
+ tools.PrepareOutputDir(None)
+ ExtractEntries(args.image, args.filename, args.outdir, args.paths,
+ not args.uncompressed)
+ finally:
+ tools.FinaliseOutputDir()
+ return 0
+
# Try to figure out which device tree contains our image description
- if options.dt:
- dtb_fname = options.dt
+ if args.dt:
+ dtb_fname = args.dt
else:
- board = options.board
+ board = args.board
if not board:
raise ValueError('Must provide a board to process (use -b <board>)')
- board_pathname = os.path.join(options.build_dir, board)
+ board_pathname = os.path.join(args.build_dir, board)
dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
- if not options.indir:
- options.indir = ['.']
- options.indir.append(board_pathname)
+ if not args.indir:
+ args.indir = ['.']
+ args.indir.append(board_pathname)
try:
# Import these here in case libfdt.py is not available, in which case
@@ -106,13 +221,15 @@
import fdt
import fdt_util
- tout.Init(options.verbosity)
- elf.debug = options.debug
- state.use_fake_dtb = options.fake_dtb
+ tout.Init(args.verbosity)
+ elf.debug = args.debug
+ cbfs_util.VERBOSE = args.verbosity > 2
+ state.use_fake_dtb = args.fake_dtb
try:
- tools.SetInputDirs(options.indir)
- tools.PrepareOutputDir(options.outdir, options.preserve)
- state.SetEntryArgs(options.entry_arg)
+ tools.SetInputDirs(args.indir)
+ tools.PrepareOutputDir(args.outdir, args.preserve)
+ tools.SetToolPaths(args.toolpath)
+ state.SetEntryArgs(args.entry_arg)
# Get the device tree ready by compiling it and copying the compiled
# output into a file in our output directly. Then scan it for use
@@ -129,16 +246,16 @@
images = _ReadImageDesc(node)
- if options.image:
+ if args.image:
skip = []
new_images = OrderedDict()
for name, image in images.items():
- if name in options.image:
+ if name in args.image:
new_images[name] = image
else:
skip.append(name)
images = new_images
- if skip and options.verbosity >= 2:
+ if skip and args.verbosity >= 2:
print('Skipping images: %s' % ', '.join(skip))
state.Prepare(images, dtb)
@@ -152,7 +269,7 @@
# entry offsets remain the same.
for image in images.values():
image.ExpandEntries()
- if options.update_fdt:
+ if args.update_fdt:
image.AddMissingProperties()
image.ProcessFdt(dtb)
@@ -168,24 +285,45 @@
# completed and written, but that does not seem important.
image.GetEntryContents()
image.GetEntryOffsets()
- try:
- image.PackEntries()
- image.CheckSize()
- image.CheckEntries()
- except Exception as e:
- if options.map:
- fname = image.WriteMap()
- print("Wrote map file '%s' to show errors" % fname)
- raise
- image.SetImagePos()
- if options.update_fdt:
- image.SetCalculatedProperties()
- for dtb_item in state.GetFdts():
- dtb_item.Sync()
- image.ProcessEntryContents()
+
+ # We need to pack the entries to figure out where everything
+ # should be placed. This sets the offset/size of each entry.
+ # However, after packing we call ProcessEntryContents() which
+ # may result in an entry changing size. In that case we need to
+ # do another pass. Since the device tree often contains the
+ # final offset/size information we try to make space for this in
+ # AddMissingProperties() above. However, if the device is
+ # compressed we cannot know this compressed size in advance,
+ # since changing an offset from 0x100 to 0x104 (for example) can
+ # alter the compressed size of the device tree. So we need a
+ # third pass for this.
+ passes = 3
+ for pack_pass in range(passes):
+ try:
+ image.PackEntries()
+ image.CheckSize()
+ image.CheckEntries()
+ except Exception as e:
+ if args.map:
+ fname = image.WriteMap()
+ print("Wrote map file '%s' to show errors" % fname)
+ raise
+ image.SetImagePos()
+ if args.update_fdt:
+ image.SetCalculatedProperties()
+ for dtb_item in state.GetFdts():
+ dtb_item.Sync()
+ sizes_ok = image.ProcessEntryContents()
+ if sizes_ok:
+ break
+ image.ResetForPack()
+ if not sizes_ok:
+ image.Raise('Entries expanded after packing (tried %s passes)' %
+ passes)
+
image.WriteSymbols()
image.BuildImage()
- if options.map:
+ if args.map:
image.WriteMap()
# Write the updated FDTs to our output files
diff --git a/tools/binman/elf.py b/tools/binman/elf.py
index 828681d..8147b34 100644
--- a/tools/binman/elf.py
+++ b/tools/binman/elf.py
@@ -5,19 +5,39 @@
# Handle various things related to ELF images
#
+from __future__ import print_function
+
from collections import namedtuple, OrderedDict
import command
+import io
import os
import re
+import shutil
import struct
+import tempfile
import tools
+ELF_TOOLS = True
+try:
+ from elftools.elf.elffile import ELFFile
+ from elftools.elf.sections import SymbolTableSection
+except: # pragma: no cover
+ ELF_TOOLS = False
+
# This is enabled from control.py
debug = False
Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
+# Information about an ELF file:
+# data: Extracted program contents of ELF file (this would be loaded by an
+# ELF loader when reading this file
+# load: Load address of code
+# entry: Entry address of code
+# memsize: Number of bytes in memory occupied by loading this ELF file
+ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
+
def GetSymbols(fname, patterns):
"""Get the symbols from an ELF file
@@ -128,3 +148,157 @@
(msg, name, offset, value, len(value_bytes)))
entry.data = (entry.data[:offset] + value_bytes +
entry.data[offset + sym.size:])
+
+def MakeElf(elf_fname, text, data):
+ """Make an elf file with the given data in a single section
+
+ The output file has a several section including '.text' and '.data',
+ containing the info provided in arguments.
+
+ Args:
+ elf_fname: Output filename
+ text: Text (code) to put in the file's .text section
+ data: Data to put in the file's .data section
+ """
+ outdir = tempfile.mkdtemp(prefix='binman.elf.')
+ s_file = os.path.join(outdir, 'elf.S')
+
+ # Spilt the text into two parts so that we can make the entry point two
+ # bytes after the start of the text section
+ text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
+ text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
+ data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
+ with open(s_file, 'w') as fd:
+ print('''/* Auto-generated C program to produce an ELF file for testing */
+
+.section .text
+.code32
+.globl _start
+.type _start, @function
+%s
+_start:
+%s
+.ident "comment"
+
+.comm fred,8,4
+
+.section .empty
+.globl _empty
+_empty:
+.byte 1
+
+.globl ernie
+.data
+.type ernie, @object
+.size ernie, 4
+ernie:
+%s
+''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
+ file=fd)
+ lds_file = os.path.join(outdir, 'elf.lds')
+
+ # Use a linker script to set the alignment and text address.
+ with open(lds_file, 'w') as fd:
+ print('''/* Auto-generated linker script to produce an ELF file for testing */
+
+PHDRS
+{
+ text PT_LOAD ;
+ data PT_LOAD ;
+ empty PT_LOAD FLAGS ( 6 ) ;
+ note PT_NOTE ;
+}
+
+SECTIONS
+{
+ . = 0xfef20000;
+ ENTRY(_start)
+ .text . : SUBALIGN(0)
+ {
+ *(.text)
+ } :text
+ .data : {
+ *(.data)
+ } :data
+ _bss_start = .;
+ .empty : {
+ *(.empty)
+ } :empty
+ .note : {
+ *(.comment)
+ } :note
+ .bss _bss_start (OVERLAY) : {
+ *(.bss)
+ }
+}
+''', file=fd)
+ # -static: Avoid requiring any shared libraries
+ # -nostdlib: Don't link with C library
+ # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
+ # text section at the start
+ # -m32: Build for 32-bit x86
+ # -T...: Specifies the link script, which sets the start address
+ stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
+ '-m32','-T', lds_file, '-o', elf_fname, s_file)
+ shutil.rmtree(outdir)
+
+def DecodeElf(data, location):
+ """Decode an ELF file and return information about it
+
+ Args:
+ data: Data from ELF file
+ location: Start address of data to return
+
+ Returns:
+ ElfInfo object containing information about the decoded ELF file
+ """
+ file_size = len(data)
+ with io.BytesIO(data) as fd:
+ elf = ELFFile(fd)
+ data_start = 0xffffffff;
+ data_end = 0;
+ mem_end = 0;
+ virt_to_phys = 0;
+
+ for i in range(elf.num_segments()):
+ segment = elf.get_segment(i)
+ if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
+ skipped = 1 # To make code-coverage see this line
+ continue
+ start = segment['p_paddr']
+ mend = start + segment['p_memsz']
+ rend = start + segment['p_filesz']
+ data_start = min(data_start, start)
+ data_end = max(data_end, rend)
+ mem_end = max(mem_end, mend)
+ if not virt_to_phys:
+ virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
+
+ output = bytearray(data_end - data_start)
+ for i in range(elf.num_segments()):
+ segment = elf.get_segment(i)
+ if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
+ skipped = 1 # To make code-coverage see this line
+ continue
+ start = segment['p_paddr']
+ offset = 0
+ if start < location:
+ offset = location - start
+ start = location
+ # A legal ELF file can have a program header with non-zero length
+ # but zero-length file size and a non-zero offset which, added
+ # together, are greater than input->size (i.e. the total file size).
+ # So we need to not even test in the case that p_filesz is zero.
+ # Note: All of this code is commented out since we don't have a test
+ # case for it.
+ size = segment['p_filesz']
+ #if not size:
+ #continue
+ #end = segment['p_offset'] + segment['p_filesz']
+ #if end > file_size:
+ #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
+ #file_size, end)
+ output[start - data_start:start - data_start + size] = (
+ segment.data()[offset:])
+ return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
+ mem_end - data_start)
diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py
index 42d94cb..e250637 100644
--- a/tools/binman/elf_test.py
+++ b/tools/binman/elf_test.py
@@ -5,9 +5,12 @@
# Test for the elf module
import os
+import shutil
import sys
+import tempfile
import unittest
+import command
import elf
import test_util
import tools
@@ -136,6 +139,44 @@
elf.debug = False
self.assertTrue(len(stdout.getvalue()) > 0)
+ def testMakeElf(self):
+ """Test for the MakeElf function"""
+ outdir = tempfile.mkdtemp(prefix='elf.')
+ expected_text = b'1234'
+ expected_data = b'wxyz'
+ elf_fname = os.path.join(outdir, 'elf')
+ bin_fname = os.path.join(outdir, 'elf')
+
+ # Make an Elf file and then convert it to a fkat binary file. This
+ # should produce the original data.
+ elf.MakeElf(elf_fname, expected_text, expected_data)
+ stdout = command.Output('objcopy', '-O', 'binary', elf_fname, bin_fname)
+ with open(bin_fname, 'rb') as fd:
+ data = fd.read()
+ self.assertEqual(expected_text + expected_data, data)
+ shutil.rmtree(outdir)
+
+ def testDecodeElf(self):
+ """Test for the MakeElf function"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+ outdir = tempfile.mkdtemp(prefix='elf.')
+ expected_text = b'1234'
+ expected_data = b'wxyz'
+ elf_fname = os.path.join(outdir, 'elf')
+ elf.MakeElf(elf_fname, expected_text, expected_data)
+ data = tools.ReadFile(elf_fname)
+
+ load = 0xfef20000
+ entry = load + 2
+ expected = expected_text + expected_data
+ self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)),
+ elf.DecodeElf(data, 0))
+ self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:],
+ load, entry, len(expected)),
+ elf.DecodeElf(data, load + 2))
+ #shutil.rmtree(outdir)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index d842d89..1c382f3 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -23,6 +23,7 @@
import fdt_util
import state
import tools
+import tout
modules = {}
@@ -33,6 +34,10 @@
# device-tree properties.
EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
+# Information about an entry for use when displaying summaries
+EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
+ 'image_pos', 'uncomp_size', 'offset',
+ 'entry'])
class Entry(object):
"""An Entry in the section
@@ -51,6 +56,8 @@
offset: Offset of entry within the section, None if not known yet (in
which case it will be calculated by Pack())
size: Entry size in bytes, None if not known
+ uncomp_size: Size of uncompressed data in bytes, if the entry is
+ compressed, else None
contents_size: Size of contents in bytes, 0 by default
align: Entry start offset alignment, or None
align_size: Entry size alignment, or None
@@ -58,6 +65,9 @@
pad_before: Number of pad bytes before the contents, 0 if none
pad_after: Number of pad bytes after the contents, 0 if none
data: Contents of entry (string of bytes)
+ compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
+ orig_offset: Original offset value read from node
+ orig_size: Original size value read from node
"""
def __init__(self, section, etype, node, read_node=True, name_prefix=''):
self.section = section
@@ -66,6 +76,7 @@
self.name = node and (name_prefix + node.name) or 'none'
self.offset = None
self.size = None
+ self.uncomp_size = None
self.data = None
self.contents_size = 0
self.align = None
@@ -76,15 +87,15 @@
self.offset_unset = False
self.image_pos = None
self._expand_size = False
+ self.compress = 'none'
if read_node:
self.ReadNode()
@staticmethod
- def Lookup(section, node_path, etype):
+ def Lookup(node_path, etype):
"""Look up the entry class for a node.
Args:
- section: Section object containing this node
node_node: Path name of Node object containing information about
the entry to create (used for errors)
etype: Entry type to use
@@ -135,7 +146,7 @@
"""
if not etype:
etype = fdt_util.GetString(node, 'type', node.name)
- obj = Entry.Lookup(section, node.path, etype)
+ obj = Entry.Lookup(node.path, etype)
# Call its constructor to get the object we want.
return obj(section, etype, node)
@@ -149,6 +160,14 @@
self.Raise("Please use 'offset' instead of 'pos'")
self.offset = fdt_util.GetInt(self._node, 'offset')
self.size = fdt_util.GetInt(self._node, 'size')
+ self.orig_offset = self.offset
+ self.orig_size = self.size
+
+ # These should not be set in input files, but are set in an FDT map,
+ # which is also read by this code.
+ self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
+ self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
+
self.align = fdt_util.GetInt(self._node, 'align')
if tools.NotPowerOfTwo(self.align):
raise ValueError("Node '%s': Alignment %s must be a power of two" %
@@ -157,8 +176,8 @@
self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
self.align_size = fdt_util.GetInt(self._node, 'align-size')
if tools.NotPowerOfTwo(self.align_size):
- raise ValueError("Node '%s': Alignment size %s must be a power "
- "of two" % (self._node.path, self.align_size))
+ self.Raise("Alignment size %s must be a power of two" %
+ self.align_size)
self.align_end = fdt_util.GetInt(self._node, 'align-end')
self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
@@ -188,6 +207,8 @@
for prop in ['offset', 'size', 'image-pos']:
if not prop in self._node.props:
state.AddZeroProp(self._node, prop)
+ if self.compress != 'none':
+ state.AddZeroProp(self._node, 'uncomp-size')
err = state.CheckAddHashProp(self._node)
if err:
self.Raise(err)
@@ -196,8 +217,10 @@
"""Set the value of device-tree properties calculated by binman"""
state.SetInt(self._node, 'offset', self.offset)
state.SetInt(self._node, 'size', self.size)
- state.SetInt(self._node, 'image-pos',
- self.image_pos - self.section.GetRootSkipAtStart())
+ base = self.section.GetRootSkipAtStart() if self.section else 0
+ state.SetInt(self._node, 'image-pos', self.image_pos - base)
+ if self.uncomp_size is not None:
+ state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
state.CheckSetHashValue(self._node, self.GetData)
def ProcessFdt(self, fdt):
@@ -229,26 +252,36 @@
This sets both the data and content_size properties
Args:
- data: Data to set to the contents (string)
+ data: Data to set to the contents (bytes)
"""
self.data = data
self.contents_size = len(self.data)
def ProcessContentsUpdate(self, data):
- """Update the contens of an entry, after the size is fixed
+ """Update the contents of an entry, after the size is fixed
- This checks that the new data is the same size as the old.
+ This checks that the new data is the same size as the old. If the size
+ has changed, this triggers a re-run of the packing algorithm.
Args:
- data: Data to set to the contents (string)
+ data: Data to set to the contents (bytes)
Raises:
ValueError if the new data size is not the same as the old
"""
- if len(data) != self.contents_size:
+ size_ok = True
+ new_size = len(data)
+ if state.AllowEntryExpansion():
+ if new_size > self.contents_size:
+ tout.Debug("Entry '%s' size change from %#x to %#x" % (
+ self._node.path, self.contents_size, new_size))
+ # self.data will indicate the new size needed
+ size_ok = False
+ elif new_size != self.contents_size:
self.Raise('Cannot update entry size from %d to %d' %
- (len(data), self.contents_size))
+ (self.contents_size, new_size))
self.SetContents(data)
+ return size_ok
def ObtainContents(self):
"""Figure out the contents of an entry.
@@ -260,6 +293,11 @@
# No contents by default: subclasses can implement this
return True
+ def ResetForPack(self):
+ """Reset offset/size fields so that packing can be done again"""
+ self.offset = self.orig_offset
+ self.size = self.orig_size
+
def Pack(self, offset):
"""Figure out how to pack the entry into the section
@@ -355,11 +393,34 @@
return self.data
def GetOffsets(self):
+ """Get the offsets for siblings
+
+ Some entry types can contain information about the position or size of
+ other entries. An example of this is the Intel Flash Descriptor, which
+ knows where the Intel Management Engine section should go.
+
+ If this entry knows about the position of other entries, it can specify
+ this by returning values here
+
+ Returns:
+ Dict:
+ key: Entry type
+ value: List containing position and size of the given entry
+ type. Either can be None if not known
+ """
return {}
+ def SetOffsetSize(self, offset, size):
+ """Set the offset and/or size of an entry
+
- def SetOffsetSize(self, pos, size):
- self.offset = pos
- self.size = size
+ Args:
+ offset: New offset, or None to leave alone
+ size: New size, or None to leave alone
+ """
+ if offset is not None:
+ self.offset = offset
+ if size is not None:
+ self.size = size
def SetImagePos(self, image_pos):
"""Set the position in the image
@@ -370,7 +431,22 @@
self.image_pos = image_pos + self.offset
def ProcessContents(self):
- pass
+ """Do any post-packing updates of entry contents
+
+ This function should call ProcessContentsUpdate() to update the entry
+ contents, if necessary, returning its return value here.
+
+ Args:
+ data: Data to set to the contents (bytes)
+
+ Returns:
+ True if the new data size is OK, False if expansion is needed
+
+ Raises:
+ ValueError if the new data size is not the same as the old and
+ state.AllowEntryExpansion() is False
+ """
+ return True
def WriteSymbols(self, section):
"""Write symbol values into binary files for access at run time
@@ -482,7 +558,9 @@
modules.remove('_testing')
missing = []
for name in modules:
- module = Entry.Lookup(name, name, name)
+ if name.startswith('__'):
+ continue
+ module = Entry.Lookup(name, name)
docs = getattr(module, '__doc__')
if test_missing == name:
docs = None
@@ -529,3 +607,76 @@
# the data grows. This should not fail, but check it to be sure.
if not self.ObtainContents():
self.Raise('Cannot obtain contents when expanding entry')
+
+ def HasSibling(self, name):
+ """Check if there is a sibling of a given name
+
+ Returns:
+ True if there is an entry with this name in the the same section,
+ else False
+ """
+ return name in self.section.GetEntries()
+
+ def GetSiblingImagePos(self, name):
+ """Return the image position of the given sibling
+
+ Returns:
+ Image position of sibling, or None if the sibling has no position,
+ or False if there is no such sibling
+ """
+ if not self.HasSibling(name):
+ return False
+ return self.section.GetEntries()[name].image_pos
+
+ @staticmethod
+ def AddEntryInfo(entries, indent, name, etype, size, image_pos,
+ uncomp_size, offset, entry):
+ """Add a new entry to the entries list
+
+ Args:
+ entries: List (of EntryInfo objects) to add to
+ indent: Current indent level to add to list
+ name: Entry name (string)
+ etype: Entry type (string)
+ size: Entry size in bytes (int)
+ image_pos: Position within image in bytes (int)
+ uncomp_size: Uncompressed size if the entry uses compression, else
+ None
+ offset: Entry offset within parent in bytes (int)
+ entry: Entry object
+ """
+ entries.append(EntryInfo(indent, name, etype, size, image_pos,
+ uncomp_size, offset, entry))
+
+ def ListEntries(self, entries, indent):
+ """Add files in this entry to the list of entries
+
+ This can be overridden by subclasses which need different behaviour.
+
+ Args:
+ entries: List (of EntryInfo objects) to add to
+ indent: Current indent level to add to list
+ """
+ self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
+ self.image_pos, self.uncomp_size, self.offset, self)
+
+ def ReadData(self, decomp=True):
+ """Read the data for an entry from the image
+
+ This is used when the image has been read in and we want to extract the
+ data for a particular entry from that image.
+
+ Args:
+ decomp: True to decompress any compressed data before returning it;
+ False to return the raw, uncompressed data
+
+ Returns:
+ Entry data (bytes)
+ """
+ # Use True here so that we get an uncompressed section to work from,
+ # although compressed sections are currently not supported
+ data = self.section.ReadData(True)
+ tout.Info('%s: Reading data from offset %#x-%#x, size %#x (avail %#x)' %
+ (self.GetPath(), self.offset, self.offset + self.size,
+ self.size, len(data)))
+ return data[self.offset:self.offset + self.size]
diff --git a/tools/binman/entry_test.py b/tools/binman/entry_test.py
index b30a7be..b6ad3ed 100644
--- a/tools/binman/entry_test.py
+++ b/tools/binman/entry_test.py
@@ -9,12 +9,11 @@
import sys
import unittest
+import entry
import fdt
import fdt_util
import tools
-entry = None
-
class TestEntry(unittest.TestCase):
def setUp(self):
tools.PrepareOutputDir(None)
@@ -29,16 +28,7 @@
dtb = fdt.FdtScan(fname)
return dtb.GetNode('/binman/u-boot')
- def test1EntryNoImportLib(self):
- """Test that we can import Entry subclassess successfully"""
-
- sys.modules['importlib'] = None
- global entry
- import entry
- entry.Entry.Create(None, self.GetNode(), 'u-boot')
-
- def test2EntryImportLib(self):
- del sys.modules['importlib']
+ def _ReloadEntry(self):
global entry
if entry:
if sys.version_info[0] >= 3:
@@ -48,8 +38,21 @@
reload(entry)
else:
import entry
+
+ def test1EntryNoImportLib(self):
+ """Test that we can import Entry subclassess successfully"""
+ sys.modules['importlib'] = None
+ global entry
+ self._ReloadEntry()
+ entry.Entry.Create(None, self.GetNode(), 'u-boot')
+ self.assertFalse(entry.have_importlib)
+
+ def test2EntryImportLib(self):
+ del sys.modules['importlib']
+ global entry
+ self._ReloadEntry()
entry.Entry.Create(None, self.GetNode(), 'u-boot-spl')
- del entry
+ self.assertTrue(entry.have_importlib)
def testEntryContents(self):
"""Test the Entry bass class"""
@@ -59,7 +62,6 @@
def testUnknownEntry(self):
"""Test that unknown entry types are detected"""
- import entry
Node = collections.namedtuple('Node', ['name', 'path'])
node = Node('invalid-name', 'invalid-path')
with self.assertRaises(ValueError) as e:
@@ -69,7 +71,6 @@
def testUniqueName(self):
"""Test Entry.GetUniqueName"""
- import entry
Node = collections.namedtuple('Node', ['name', 'parent'])
base_node = Node('root', None)
base_entry = entry.Entry(None, None, base_node, read_node=False)
@@ -80,7 +81,6 @@
def testGetDefaultFilename(self):
"""Trivial test for this base class function"""
- import entry
base_entry = entry.Entry(None, None, None, read_node=False)
self.assertIsNone(base_entry.GetDefaultFilename())
diff --git a/tools/binman/etype/__init__.py b/tools/binman/etype/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/binman/etype/__init__.py
diff --git a/tools/binman/etype/_testing.py b/tools/binman/etype/_testing.py
index ac62d2e..ae24fe8 100644
--- a/tools/binman/etype/_testing.py
+++ b/tools/binman/etype/_testing.py
@@ -50,6 +50,8 @@
'bad-update-contents')
self.return_contents_once = fdt_util.GetBool(self._node,
'return-contents-once')
+ self.bad_update_contents_twice = fdt_util.GetBool(self._node,
+ 'bad-update-contents-twice')
# Set to True when the entry is ready to process the FDT.
self.process_fdt_ready = False
@@ -71,11 +73,12 @@
if self.force_bad_datatype:
self.GetEntryArgsOrProps([EntryArg('test-bad-datatype-arg', bool)])
self.return_contents = True
+ self.contents = b'a'
def ObtainContents(self):
if self.return_unknown_contents or not self.return_contents:
return False
- self.data = b'a'
+ self.data = self.contents
self.contents_size = len(self.data)
if self.return_contents_once:
self.return_contents = False
@@ -88,9 +91,14 @@
def ProcessContents(self):
if self.bad_update_contents:
- # Request to update the conents with something larger, to cause a
+ # Request to update the contents with something larger, to cause a
# failure.
- self.ProcessContentsUpdate('aa')
+ if self.bad_update_contents_twice:
+ self.contents += b'a'
+ else:
+ self.contents = b'aa'
+ return self.ProcessContentsUpdate(self.contents)
+ return True
def ProcessFdt(self, fdt):
"""Force reprocessing the first time"""
diff --git a/tools/binman/etype/blob.py b/tools/binman/etype/blob.py
index f56a1f8..00cad33 100644
--- a/tools/binman/etype/blob.py
+++ b/tools/binman/etype/blob.py
@@ -9,6 +9,7 @@
import fdt_util
import state
import tools
+import tout
class Entry_blob(Entry):
"""Entry containing an arbitrary binary blob
@@ -33,8 +34,7 @@
def __init__(self, section, etype, node):
Entry.__init__(self, section, etype, node)
self._filename = fdt_util.GetString(self._node, 'filename', self.etype)
- self._compress = fdt_util.GetString(self._node, 'compress', 'none')
- self._uncompressed_size = None
+ self.compress = fdt_util.GetString(self._node, 'compress', 'none')
def ObtainContents(self):
self._filename = self.GetDefaultFilename()
@@ -42,37 +42,40 @@
self.ReadBlobContents()
return True
+ def CompressData(self, indata):
+ if self.compress != 'none':
+ self.uncomp_size = len(indata)
+ data = tools.Compress(indata, self.compress)
+ return data
+
def ReadBlobContents(self):
- # We assume the data is small enough to fit into memory. If this
- # is used for large filesystem image that might not be true.
- # In that case, Image.BuildImage() could be adjusted to use a
- # new Entry method which can read in chunks. Then we could copy
- # the data in chunks and avoid reading it all at once. For now
- # this seems like an unnecessary complication.
- data = tools.ReadFile(self._pathname)
- if self._compress == 'lz4':
- self._uncompressed_size = len(data)
- '''
- import lz4 # Import this only if needed (python-lz4 dependency)
+ """Read blob contents into memory
- try:
- data = lz4.frame.compress(data)
- except AttributeError:
- data = lz4.compress(data)
- '''
- data = tools.Run('lz4', '-c', self._pathname, binary=True)
+ This function compresses the data before storing if needed.
+
+ We assume the data is small enough to fit into memory. If this
+ is used for large filesystem image that might not be true.
+ In that case, Image.BuildImage() could be adjusted to use a
+ new Entry method which can read in chunks. Then we could copy
+ the data in chunks and avoid reading it all at once. For now
+ this seems like an unnecessary complication.
+ """
+ indata = tools.ReadFile(self._pathname)
+ data = self.CompressData(indata)
self.SetContents(data)
return True
def GetDefaultFilename(self):
return self._filename
- def AddMissingProperties(self):
- Entry.AddMissingProperties(self)
- if self._compress != 'none':
- state.AddZeroProp(self._node, 'uncomp-size')
-
- def SetCalculatedProperties(self):
- Entry.SetCalculatedProperties(self)
- if self._uncompressed_size is not None:
- state.SetInt(self._node, 'uncomp-size', self._uncompressed_size)
+ def ReadData(self, decomp=True):
+ indata = Entry.ReadData(self, decomp)
+ if decomp:
+ data = tools.Decompress(indata, self.compress)
+ if self.uncomp_size:
+ tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
+ (self.GetPath(), len(indata), self.compress,
+ len(data)))
+ else:
+ data = indata
+ return data
diff --git a/tools/binman/etype/blob_dtb.py b/tools/binman/etype/blob_dtb.py
index cc5b4a3..88ed55d 100644
--- a/tools/binman/etype/blob_dtb.py
+++ b/tools/binman/etype/blob_dtb.py
@@ -23,11 +23,11 @@
def ObtainContents(self):
"""Get the device-tree from the list held by the 'state' module"""
self._filename = self.GetDefaultFilename()
- self._pathname, data = state.GetFdtContents(self._filename)
- self.SetContents(data)
- return True
+ self._pathname, _ = state.GetFdtContents(self._filename)
+ return Entry_blob.ReadBlobContents(self)
def ProcessContents(self):
"""Re-read the DTB contents so that we get any calculated properties"""
- _, data = state.GetFdtContents(self._filename)
- self.SetContents(data)
+ _, indata = state.GetFdtContents(self._filename)
+ data = self.CompressData(indata)
+ return self.ProcessContentsUpdate(data)
diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py
new file mode 100644
index 0000000..edf2189
--- /dev/null
+++ b/tools/binman/etype/cbfs.py
@@ -0,0 +1,263 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2019 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for a Coreboot Filesystem (CBFS)
+#
+
+from collections import OrderedDict
+
+import cbfs_util
+from cbfs_util import CbfsWriter
+from entry import Entry
+import fdt_util
+import state
+
+class Entry_cbfs(Entry):
+ """Entry containing a Coreboot Filesystem (CBFS)
+
+ A CBFS provides a way to group files into a group. It has a simple directory
+ structure and allows the position of individual files to be set, since it is
+ designed to support execute-in-place in an x86 SPI-flash device. Where XIP
+ is not used, it supports compression and storing ELF files.
+
+ CBFS is used by coreboot as its way of orgnanising SPI-flash contents.
+
+ The contents of the CBFS are defined by subnodes of the cbfs entry, e.g.:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+
+ This creates a CBFS 1MB in size two files in it: u-boot.bin and u-boot.dtb.
+ Note that the size is required since binman does not support calculating it.
+ The contents of each entry is just what binman would normally provide if it
+ were not a CBFS node. A blob type can be used to import arbitrary files as
+ with the second subnode below:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot {
+ cbfs-name = "BOOT";
+ cbfs-type = "raw";
+ };
+
+ dtb {
+ type = "blob";
+ filename = "u-boot.dtb";
+ cbfs-type = "raw";
+ cbfs-compress = "lz4";
+ cbfs-offset = <0x100000>;
+ };
+ };
+
+ This creates a CBFS 1MB in size with u-boot.bin (named "BOOT") and
+ u-boot.dtb (named "dtb") and compressed with the lz4 algorithm.
+
+
+ Properties supported in the top-level CBFS node:
+
+ cbfs-arch:
+ Defaults to "x86", but you can specify the architecture if needed.
+
+
+ Properties supported in the CBFS entry subnodes:
+
+ cbfs-name:
+ This is the name of the file created in CBFS. It defaults to the entry
+ name (which is the node name), but you can override it with this
+ property.
+
+ cbfs-type:
+ This is the CBFS file type. The following are supported:
+
+ raw:
+ This is a 'raw' file, although compression is supported. It can be
+ used to store any file in CBFS.
+
+ stage:
+ This is an ELF file that has been loaded (i.e. mapped to memory), so
+ appears in the CBFS as a flat binary. The input file must be an ELF
+ image, for example this puts "u-boot" (the ELF image) into a 'stage'
+ entry:
+
+ cbfs {
+ size = <0x100000>;
+ u-boot-elf {
+ cbfs-name = "BOOT";
+ cbfs-type = "stage";
+ };
+ };
+
+ You can use your own ELF file with something like:
+
+ cbfs {
+ size = <0x100000>;
+ something {
+ type = "blob";
+ filename = "cbfs-stage.elf";
+ cbfs-type = "stage";
+ };
+ };
+
+ As mentioned, the file is converted to a flat binary, so it is
+ equivalent to adding "u-boot.bin", for example, but with the load and
+ start addresses specified by the ELF. At present there is no option
+ to add a flat binary with a load/start address, similar to the
+ 'add-flat-binary' option in cbfstool.
+
+ cbfs-offset:
+ This is the offset of the file's data within the CBFS. It is used to
+ specify where the file should be placed in cases where a fixed position
+ is needed. Typical uses are for code which is not relocatable and must
+ execute in-place from a particular address. This works because SPI flash
+ is generally mapped into memory on x86 devices. The file header is
+ placed before this offset so that the data start lines up exactly with
+ the chosen offset. If this property is not provided, then the file is
+ placed in the next available spot.
+
+ The current implementation supports only a subset of CBFS features. It does
+ not support other file types (e.g. payload), adding multiple files (like the
+ 'files' entry with a pattern supported by binman), putting files at a
+ particular offset in the CBFS and a few other things.
+
+ Of course binman can create images containing multiple CBFSs, simply by
+ defining these in the binman config:
+
+
+ binman {
+ size = <0x800000>;
+ cbfs {
+ offset = <0x100000>;
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+
+ cbfs2 {
+ offset = <0x700000>;
+ size = <0x100000>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ image {
+ type = "blob";
+ filename = "image.jpg";
+ };
+ };
+ };
+
+ This creates an 8MB image with two CBFSs, one at offset 1MB, one at 7MB,
+ both of size 1MB.
+ """
+ def __init__(self, section, etype, node):
+ Entry.__init__(self, section, etype, node)
+ self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86')
+ self._cbfs_entries = OrderedDict()
+ self._ReadSubnodes()
+
+ def ObtainContents(self):
+ arch = cbfs_util.find_arch(self._cbfs_arg)
+ if arch is None:
+ self.Raise("Invalid architecture '%s'" % self._cbfs_arg)
+ if self.size is None:
+ self.Raise("'cbfs' entry must have a size property")
+ cbfs = CbfsWriter(self.size, arch)
+ for entry in self._cbfs_entries.values():
+ # First get the input data and put it in a file. If not available,
+ # try later.
+ if not entry.ObtainContents():
+ return False
+ data = entry.GetData()
+ cfile = None
+ if entry._type == 'raw':
+ cfile = cbfs.add_file_raw(entry._cbfs_name, data,
+ entry._cbfs_offset,
+ entry._cbfs_compress)
+ elif entry._type == 'stage':
+ cfile = cbfs.add_file_stage(entry._cbfs_name, data,
+ entry._cbfs_offset)
+ else:
+ entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" %
+ entry._type)
+ if cfile:
+ entry._cbfs_file = cfile
+ data = cbfs.get_data()
+ self.SetContents(data)
+ return True
+
+ def _ReadSubnodes(self):
+ """Read the subnodes to find out what should go in this IFWI"""
+ for node in self._node.subnodes:
+ entry = Entry.Create(self.section, node)
+ entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name)
+ entry._type = fdt_util.GetString(node, 'cbfs-type')
+ compress = fdt_util.GetString(node, 'cbfs-compress', 'none')
+ entry._cbfs_offset = fdt_util.GetInt(node, 'cbfs-offset')
+ entry._cbfs_compress = cbfs_util.find_compress(compress)
+ if entry._cbfs_compress is None:
+ self.Raise("Invalid compression in '%s': '%s'" %
+ (node.name, compress))
+ self._cbfs_entries[entry._cbfs_name] = entry
+
+ def SetImagePos(self, image_pos):
+ """Override this function to set all the entry properties from CBFS
+
+ We can only do this once image_pos is known
+
+ Args:
+ image_pos: Position of this entry in the image
+ """
+ Entry.SetImagePos(self, image_pos)
+
+ # Now update the entries with info from the CBFS entries
+ for entry in self._cbfs_entries.values():
+ cfile = entry._cbfs_file
+ entry.size = cfile.data_len
+ entry.offset = cfile.calced_cbfs_offset
+ entry.image_pos = self.image_pos + entry.offset
+ if entry._cbfs_compress:
+ entry.uncomp_size = cfile.memlen
+
+ def AddMissingProperties(self):
+ Entry.AddMissingProperties(self)
+ for entry in self._cbfs_entries.values():
+ entry.AddMissingProperties()
+ if entry._cbfs_compress:
+ state.AddZeroProp(entry._node, 'uncomp-size')
+ # Store the 'compress' property, since we don't look at
+ # 'cbfs-compress' in Entry.ReadData()
+ state.AddString(entry._node, 'compress',
+ cbfs_util.compress_name(entry._cbfs_compress))
+
+ def SetCalculatedProperties(self):
+ """Set the value of device-tree properties calculated by binman"""
+ Entry.SetCalculatedProperties(self)
+ for entry in self._cbfs_entries.values():
+ state.SetInt(entry._node, 'offset', entry.offset)
+ state.SetInt(entry._node, 'size', entry.size)
+ state.SetInt(entry._node, 'image-pos', entry.image_pos)
+ if entry.uncomp_size is not None:
+ state.SetInt(entry._node, 'uncomp-size', entry.uncomp_size)
+
+ def ListEntries(self, entries, indent):
+ """Override this method to list all files in the section"""
+ Entry.ListEntries(self, entries, indent)
+ for entry in self._cbfs_entries.values():
+ entry.ListEntries(entries, indent + 1)
+
+ def GetEntries(self):
+ return self._cbfs_entries
diff --git a/tools/binman/etype/fdtmap.py b/tools/binman/etype/fdtmap.py
new file mode 100644
index 0000000..ddb9738
--- /dev/null
+++ b/tools/binman/etype/fdtmap.py
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2018 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+
+"""# Entry-type module for a full map of the firmware image
+
+This handles putting an FDT into the image with just the information about the
+image.
+"""
+
+import libfdt
+
+from entry import Entry
+from fdt import Fdt
+import state
+import tools
+
+FDTMAP_MAGIC = b'_FDTMAP_'
+FDTMAP_HDR_LEN = 16
+
+def LocateFdtmap(data):
+ """Search an image for an fdt map
+
+ Args:
+ data: Data to search
+
+ Returns:
+ Position of fdt map in data, or None if not found. Note that the
+ position returned is of the FDT header, i.e. before the FDT data
+ """
+ hdr_pos = data.find(FDTMAP_MAGIC)
+ size = len(data)
+ if hdr_pos != -1:
+ hdr = data[hdr_pos:hdr_pos + FDTMAP_HDR_LEN]
+ if len(hdr) == FDTMAP_HDR_LEN:
+ return hdr_pos
+ return None
+
+class Entry_fdtmap(Entry):
+ """An entry which contains an FDT map
+
+ Properties / Entry arguments:
+ None
+
+ An FDT map is just a header followed by an FDT containing a list of all the
+ entries in the image. The root node corresponds to the image node in the
+ original FDT, and an image-name property indicates the image name in that
+ original tree.
+
+ The header is the string _FDTMAP_ followed by 8 unused bytes.
+
+ When used, this entry will be populated with an FDT map which reflects the
+ entries in the current image. Hierarchy is preserved, and all offsets and
+ sizes are included.
+
+ Note that the -u option must be provided to ensure that binman updates the
+ FDT with the position of each entry.
+
+ Example output for a simple image with U-Boot and an FDT map:
+
+ / {
+ size = <0x00000112>;
+ image-pos = <0x00000000>;
+ offset = <0x00000000>;
+ u-boot {
+ size = <0x00000004>;
+ image-pos = <0x00000000>;
+ offset = <0x00000000>;
+ };
+ fdtmap {
+ size = <0x0000010e>;
+ image-pos = <0x00000004>;
+ offset = <0x00000004>;
+ };
+ };
+ """
+ def __init__(self, section, etype, node):
+ Entry.__init__(self, section, etype, node)
+
+ def _GetFdtmap(self):
+ """Build an FDT map from the entries in the current image
+
+ Returns:
+ FDT map binary data
+ """
+ def _AddNode(node):
+ """Add a node to the FDT map"""
+ for pname, prop in node.props.items():
+ fsw.property(pname, prop.bytes)
+ for subnode in node.subnodes:
+ with fsw.add_node(subnode.name):
+ _AddNode(subnode)
+
+ # Get the FDT data into an Fdt object
+ data = state.GetFdtContents()[1]
+ infdt = Fdt.FromData(data)
+ infdt.Scan()
+
+ # Find the node for the image containing the Fdt-map entry
+ path = self.section.GetPath()
+ node = infdt.GetNode(path)
+ if not node:
+ self.Raise("Internal error: Cannot locate node for path '%s'" %
+ path)
+
+ # Build a new tree with all nodes and properties starting from that node
+ fsw = libfdt.FdtSw()
+ fsw.finish_reservemap()
+ with fsw.add_node(''):
+ _AddNode(node)
+ fdt = fsw.as_fdt()
+
+ # Pack this new FDT and return its contents
+ fdt.pack()
+ outfdt = Fdt.FromData(fdt.as_bytearray())
+ data = FDTMAP_MAGIC + tools.GetBytes(0, 8) + outfdt.GetContents()
+ return data
+
+ def ObtainContents(self):
+ """Obtain a placeholder for the fdt-map contents"""
+ self.SetContents(self._GetFdtmap())
+ return True
+
+ def ProcessContents(self):
+ """Write an updated version of the FDT map to this entry
+
+ This is necessary since new data may have been written back to it during
+ processing, e.g. the image-pos properties.
+ """
+ return self.ProcessContentsUpdate(self._GetFdtmap())
diff --git a/tools/binman/etype/files.py b/tools/binman/etype/files.py
index 99f2f2f..0068b30 100644
--- a/tools/binman/etype/files.py
+++ b/tools/binman/etype/files.py
@@ -14,7 +14,6 @@
import state
import tools
-import bsection
class Entry_files(Entry_section):
"""Entry containing a set of files
@@ -54,4 +53,4 @@
state.AddString(subnode, 'compress', self._compress)
# Read entries again, now that we have some
- self._section._ReadEntries()
+ self._ReadEntries()
diff --git a/tools/binman/etype/fmap.py b/tools/binman/etype/fmap.py
index e6b5c5c..f8d8d86 100644
--- a/tools/binman/etype/fmap.py
+++ b/tools/binman/etype/fmap.py
@@ -49,7 +49,7 @@
areas.append(fmap_util.FmapArea(pos or 0, entry.size or 0,
tools.FromUnicode(entry.name), 0))
- entries = self.section._image.GetEntries()
+ entries = self.section.image.GetEntries()
areas = []
for entry in entries.values():
_AddEntries(areas, entry)
@@ -62,4 +62,4 @@
return True
def ProcessContents(self):
- self.SetContents(self._GetFmap())
+ return self.ProcessContentsUpdate(self._GetFmap())
diff --git a/tools/binman/etype/image_header.py b/tools/binman/etype/image_header.py
new file mode 100644
index 0000000..8f9c5aa
--- /dev/null
+++ b/tools/binman/etype/image_header.py
@@ -0,0 +1,99 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2018 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+
+"""Entry-type module for an image header which points to the FDT map
+
+This creates an 8-byte entry with a magic number and the offset of the FDT map
+(which is another entry in the image), relative to the start or end of the
+image.
+"""
+
+import struct
+
+from entry import Entry
+import fdt_util
+
+IMAGE_HEADER_MAGIC = b'BinM'
+IMAGE_HEADER_LEN = 8
+
+def LocateHeaderOffset(data):
+ """Search an image for an image header
+
+ Args:
+ data: Data to search
+
+ Returns:
+ Offset of image header in the image, or None if not found
+ """
+ hdr_pos = data.find(IMAGE_HEADER_MAGIC)
+ if hdr_pos != -1:
+ size = len(data)
+ hdr = data[hdr_pos:hdr_pos + IMAGE_HEADER_LEN]
+ if len(hdr) == IMAGE_HEADER_LEN:
+ offset = struct.unpack('<I', hdr[4:])[0]
+ if hdr_pos == len(data) - IMAGE_HEADER_LEN:
+ pos = size + offset - (1 << 32)
+ else:
+ pos = offset
+ return pos
+ return None
+
+class Entry_image_header(Entry):
+ """An entry which contains a pointer to the FDT map
+
+ Properties / Entry arguments:
+ location: Location of header ("start" or "end" of image). This is
+ optional. If omitted then the entry must have an offset property.
+
+ This adds an 8-byte entry to the start or end of the image, pointing to the
+ location of the FDT map. The format is a magic number followed by an offset
+ from the start or end of the image, in twos-compliment format.
+
+ This entry must be in the top-level part of the image.
+
+ NOTE: If the location is at the start/end, you will probably need to specify
+ sort-by-offset for the image, unless you actually put the image header
+ first/last in the entry list.
+ """
+ def __init__(self, section, etype, node):
+ Entry.__init__(self, section, etype, node)
+ self.location = fdt_util.GetString(self._node, 'location')
+
+ def _GetHeader(self):
+ image_pos = self.GetSiblingImagePos('fdtmap')
+ if image_pos == False:
+ self.Raise("'image_header' section must have an 'fdtmap' sibling")
+ elif image_pos is None:
+ # This will be available when called from ProcessContents(), but not
+ # when called from ObtainContents()
+ offset = 0xffffffff
+ else:
+ image_size = self.section.GetImageSize() or 0
+ base = (0 if self.location != 'end' else image_size)
+ offset = (image_pos - base) & 0xffffffff
+ data = IMAGE_HEADER_MAGIC + struct.pack('<I', offset)
+ return data
+
+ def ObtainContents(self):
+ """Obtain a placeholder for the header contents"""
+ self.SetContents(self._GetHeader())
+ return True
+
+ def Pack(self, offset):
+ """Special pack method to set the offset to start/end of image"""
+ if not self.offset:
+ if self.location not in ['start', 'end']:
+ self.Raise("Invalid location '%s', expected 'start' or 'end'" %
+ self.location)
+ image_size = self.section.GetImageSize() or 0
+ self.offset = (0 if self.location != 'end' else image_size - 8)
+ return Entry.Pack(self, offset)
+
+ def ProcessContents(self):
+ """Write an updated version of the FDT map to this entry
+
+ This is necessary since image_pos is not available when ObtainContents()
+ is called, since by then the entries have not been packed in the image.
+ """
+ return self.ProcessContentsUpdate(self._GetHeader())
diff --git a/tools/binman/etype/intel_descriptor.py b/tools/binman/etype/intel_descriptor.py
index 6acbbd8..adea578 100644
--- a/tools/binman/etype/intel_descriptor.py
+++ b/tools/binman/etype/intel_descriptor.py
@@ -47,17 +47,25 @@
def __init__(self, section, etype, node):
Entry_blob.__init__(self, section, etype, node)
self._regions = []
+ if self.offset is None:
+ self.offset = self.section.GetStartOffset()
def GetOffsets(self):
offset = self.data.find(FD_SIGNATURE)
if offset == -1:
- self.Raise('Cannot find FD signature')
+ self.Raise('Cannot find Intel Flash Descriptor (FD) signature')
flvalsig, flmap0, flmap1, flmap2 = struct.unpack('<LLLL',
self.data[offset:offset + 16])
frba = ((flmap0 >> 16) & 0xff) << 4
for i in range(MAX_REGIONS):
self._regions.append(Region(self.data, frba, i))
- # Set the offset for ME only, for now, since the others are not used
- return {'intel-me': [self._regions[REGION_ME].base,
- self._regions[REGION_ME].size]}
+ # Set the offset for ME (Management Engine) and IFWI (Integrated
+ # Firmware Image), for now, since the others are not used.
+ info = {}
+ if self.HasSibling('intel-me'):
+ info['intel-me'] = [self._regions[REGION_ME].base,
+ self._regions[REGION_ME].size]
+ if self.HasSibling('intel-ifwi'):
+ info['intel-ifwi'] = [self._regions[REGION_BIOS].base, None]
+ return info
diff --git a/tools/binman/etype/intel_ifwi.py b/tools/binman/etype/intel_ifwi.py
new file mode 100644
index 0000000..8c79b2d
--- /dev/null
+++ b/tools/binman/etype/intel_ifwi.py
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for Intel Management Engine binary blob
+#
+
+from collections import OrderedDict
+
+from entry import Entry
+from blob import Entry_blob
+import fdt_util
+import tools
+
+class Entry_intel_ifwi(Entry_blob):
+ """Entry containing an Intel Integrated Firmware Image (IFWI) file
+
+ Properties / Entry arguments:
+ - filename: Filename of file to read into entry. This is either the
+ IFWI file itself, or a file that can be converted into one using a
+ tool
+ - convert-fit: If present this indicates that the ifwitool should be
+ used to convert the provided file into a IFWI.
+
+ This file contains code and data used by the SoC that is required to make
+ it work. It includes U-Boot TPL, microcode, things related to the CSE
+ (Converged Security Engine, the microcontroller that loads all the firmware)
+ and other items beyond the wit of man.
+
+ A typical filename is 'ifwi.bin' for an IFWI file, or 'fitimage.bin' for a
+ file that will be converted to an IFWI.
+
+ The position of this entry is generally set by the intel-descriptor entry.
+
+ The contents of the IFWI are specified by the subnodes of the IFWI node.
+ Each subnode describes an entry which is placed into the IFWFI with a given
+ sub-partition (and optional entry name).
+
+ See README.x86 for information about x86 binary blobs.
+ """
+ def __init__(self, section, etype, node):
+ Entry_blob.__init__(self, section, etype, node)
+ self._convert_fit = fdt_util.GetBool(self._node, 'convert-fit')
+ self._ifwi_entries = OrderedDict()
+ self._ReadSubnodes()
+
+ def ObtainContents(self):
+ """Get the contects for the IFWI
+
+ Unfortunately we cannot create anything from scratch here, as Intel has
+ tools which create precursor binaries with lots of data and settings,
+ and these are not incorporated into binman.
+
+ The first step is to get a file in the IFWI format. This is either
+ supplied directly or is extracted from a fitimage using the 'create'
+ subcommand.
+
+ After that we delete the OBBP sub-partition and add each of the files
+ that we want in the IFWI file, one for each sub-entry of the IWFI node.
+ """
+ self._pathname = tools.GetInputFilename(self._filename)
+
+ # Create the IFWI file if needed
+ if self._convert_fit:
+ inname = self._pathname
+ outname = tools.GetOutputFilename('ifwi.bin')
+ tools.RunIfwiTool(inname, tools.CMD_CREATE, outname)
+ self._filename = 'ifwi.bin'
+ self._pathname = outname
+ else:
+ # Provide a different code path here to ensure we have test coverage
+ inname = self._pathname
+
+ # Delete OBBP if it is there, then add the required new items.
+ tools.RunIfwiTool(inname, tools.CMD_DELETE, subpart='OBBP')
+
+ for entry in self._ifwi_entries.values():
+ # First get the input data and put it in a file
+ if not entry.ObtainContents():
+ return False
+ data = entry.GetData()
+ uniq = self.GetUniqueName()
+ input_fname = tools.GetOutputFilename('input.%s' % uniq)
+ tools.WriteFile(input_fname, data)
+
+ tools.RunIfwiTool(inname,
+ tools.CMD_REPLACE if entry._ifwi_replace else tools.CMD_ADD,
+ input_fname, entry._ifwi_subpart, entry._ifwi_entry_name)
+
+ self.ReadBlobContents()
+ return True
+
+ def _ReadSubnodes(self):
+ """Read the subnodes to find out what should go in this IFWI"""
+ for node in self._node.subnodes:
+ entry = Entry.Create(self.section, node)
+ entry._ifwi_replace = fdt_util.GetBool(node, 'replace')
+ entry._ifwi_subpart = fdt_util.GetString(node, 'ifwi-subpart')
+ entry._ifwi_entry_name = fdt_util.GetString(node, 'ifwi-entry')
+ self._ifwi_entries[entry._ifwi_subpart] = entry
diff --git a/tools/binman/etype/intel_me.py b/tools/binman/etype/intel_me.py
index 247c5b3..c932ec5 100644
--- a/tools/binman/etype/intel_me.py
+++ b/tools/binman/etype/intel_me.py
@@ -22,6 +22,8 @@
A typical filename is 'me.bin'.
+ The position of this entry is generally set by the intel-descriptor entry.
+
See README.x86 for information about x86 binary blobs.
"""
def __init__(self, section, etype, node):
diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index 3681a48..6db3c7a 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -1,59 +1,155 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2018 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
-#
-# Entry-type module for sections, which are entries which can contain other
-# entries.
-#
+
+"""Entry-type module for sections (groups of entries)
+
+Sections are entries which can contain other entries. This allows hierarchical
+images to be created.
+"""
+
+from __future__ import print_function
+
+from collections import OrderedDict
+import re
+import sys
from entry import Entry
import fdt_util
import tools
-import bsection
class Entry_section(Entry):
"""Entry that contains other entries
Properties / Entry arguments: (see binman README for more information)
- - size: Size of section in bytes
- - align-size: Align size to a particular power of two
- - pad-before: Add padding before the entry
- - pad-after: Add padding after the entry
- - pad-byte: Pad byte to use when padding
- - sort-by-offset: Reorder the entries by offset
- - end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
- - name-prefix: Adds a prefix to the name of every entry in the section
+ pad-byte: Pad byte to use when padding
+ sort-by-offset: True if entries should be sorted by offset, False if
+ they must be in-order in the device tree description
+ end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
+ skip-at-start: Number of bytes before the first entry starts. These
+ effectively adjust the starting offset of entries. For example,
+ if this is 16, then the first entry would start at 16. An entry
+ with offset = 20 would in fact be written at offset 4 in the image
+ file, since the first 16 bytes are skipped when writing.
+ name-prefix: Adds a prefix to the name of every entry in the section
when writing out the map
+ Since a section is also an entry, it inherits all the properies of entries
+ too.
+
A section is an entry which can contain other entries, thus allowing
hierarchical images to be created. See 'Sections and hierarchical images'
in the binman README for more information.
"""
- def __init__(self, section, etype, node):
- Entry.__init__(self, section, etype, node)
- self._section = bsection.Section(node.name, section, node,
- section._image)
+ def __init__(self, section, etype, node, test=False):
+ if not test:
+ Entry.__init__(self, section, etype, node)
+ if section:
+ self.image = section.image
+ self._entries = OrderedDict()
+ self._pad_byte = 0
+ self._sort = False
+ self._skip_at_start = None
+ self._end_4gb = False
+ if not test:
+ self._ReadNode()
+ self._ReadEntries()
+
+ def _Raise(self, msg):
+ """Raises an error for this section
+
+ Args:
+ msg: Error message to use in the raise string
+ Raises:
+ ValueError()
+ """
+ raise ValueError("Section '%s': %s" % (self._node.path, msg))
+
+ def _ReadNode(self):
+ """Read properties from the image node"""
+ self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
+ self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
+ self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
+ self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
+ if self._end_4gb:
+ if not self.size:
+ self.Raise("Section size must be provided when using end-at-4gb")
+ if self._skip_at_start is not None:
+ self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
+ else:
+ self._skip_at_start = 0x100000000 - self.size
+ else:
+ if self._skip_at_start is None:
+ self._skip_at_start = 0
+ self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
+ filename = fdt_util.GetString(self._node, 'filename')
+ if filename:
+ self._filename = filename
+
+ def _ReadEntries(self):
+ for node in self._node.subnodes:
+ if node.name == 'hash':
+ continue
+ entry = Entry.Create(self, node)
+ entry.SetPrefix(self._name_prefix)
+ self._entries[node.name] = entry
def GetFdtSet(self):
- return self._section.GetFdtSet()
+ fdt_set = set()
+ for entry in self._entries.values():
+ fdt_set.update(entry.GetFdtSet())
+ return fdt_set
def ProcessFdt(self, fdt):
- return self._section.ProcessFdt(fdt)
+ """Allow entries to adjust the device tree
+
+ Some entries need to adjust the device tree for their purposes. This
+ may involve adding or deleting properties.
+ """
+ todo = self._entries.values()
+ for passnum in range(3):
+ next_todo = []
+ for entry in todo:
+ if not entry.ProcessFdt(fdt):
+ next_todo.append(entry)
+ todo = next_todo
+ if not todo:
+ break
+ if todo:
+ self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
+ todo)
+ return True
def ExpandEntries(self):
+ """Expand out any entries which have calculated sub-entries
+
+ Some entries are expanded out at runtime, e.g. 'files', which produces
+ a section containing a list of files. Process these entries so that
+ this information is added to the device tree.
+ """
Entry.ExpandEntries(self)
- self._section.ExpandEntries()
+ for entry in self._entries.values():
+ entry.ExpandEntries()
def AddMissingProperties(self):
+ """Add new properties to the device tree as needed for this entry"""
Entry.AddMissingProperties(self)
- self._section.AddMissingProperties()
+ for entry in self._entries.values():
+ entry.AddMissingProperties()
def ObtainContents(self):
- return self._section.GetEntryContents()
+ return self.GetEntryContents()
def GetData(self):
- return self._section.GetData()
+ section_data = tools.GetBytes(self._pad_byte, self.size)
+
+ for entry in self._entries.values():
+ data = entry.GetData()
+ base = self.pad_before + entry.offset - self._skip_at_start
+ section_data = (section_data[:base] + data +
+ section_data[base + len(data):])
+ return section_data
def GetOffsets(self):
"""Handle entries that want to set the offset/size of other entries
@@ -61,35 +157,94 @@
This calls each entry's GetOffsets() method. If it returns a list
of entries to update, it updates them.
"""
- self._section.GetEntryOffsets()
+ self.GetEntryOffsets()
return {}
+ def ResetForPack(self):
+ """Reset offset/size fields so that packing can be done again"""
+ Entry.ResetForPack(self)
+ for entry in self._entries.values():
+ entry.ResetForPack()
+
def Pack(self, offset):
"""Pack all entries into the section"""
- self._section.PackEntries()
- if self._section._offset is None:
- self._section.SetOffset(offset)
- self.size = self._section.GetSize()
- return super(Entry_section, self).Pack(offset)
+ self._PackEntries()
+ return Entry.Pack(self, offset)
- def SetImagePos(self, image_pos):
- Entry.SetImagePos(self, image_pos)
- self._section.SetImagePos(image_pos + self.offset)
+ def _PackEntries(self):
+ """Pack all entries into the image"""
+ offset = self._skip_at_start
+ for entry in self._entries.values():
+ offset = entry.Pack(offset)
+ self.size = self.CheckSize()
+
+ def _ExpandEntries(self):
+ """Expand any entries that are permitted to"""
+ exp_entry = None
+ for entry in self._entries.values():
+ if exp_entry:
+ exp_entry.ExpandToLimit(entry.offset)
+ exp_entry = None
+ if entry.expand_size:
+ exp_entry = entry
+ if exp_entry:
+ exp_entry.ExpandToLimit(self.size)
+
+ def _SortEntries(self):
+ """Sort entries by offset"""
+ entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
+ self._entries.clear()
+ for entry in entries:
+ self._entries[entry._node.name] = entry
+
+ def CheckEntries(self):
+ """Check that entries do not overlap or extend outside the image"""
+ if self._sort:
+ self._SortEntries()
+ self._ExpandEntries()
+ offset = 0
+ prev_name = 'None'
+ for entry in self._entries.values():
+ entry.CheckOffset()
+ if (entry.offset < self._skip_at_start or
+ entry.offset + entry.size > self._skip_at_start +
+ self.size):
+ entry.Raise("Offset %#x (%d) is outside the section starting "
+ "at %#x (%d)" %
+ (entry.offset, entry.offset, self._skip_at_start,
+ self._skip_at_start))
+ if entry.offset < offset:
+ entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
+ "ending at %#x (%d)" %
+ (entry.offset, entry.offset, prev_name, offset, offset))
+ offset = entry.offset + entry.size
+ prev_name = entry.GetPath()
def WriteSymbols(self, section):
"""Write symbol values into binary files for access at run time"""
- self._section.WriteSymbols()
+ for entry in self._entries.values():
+ entry.WriteSymbols(self)
def SetCalculatedProperties(self):
Entry.SetCalculatedProperties(self)
- self._section.SetCalculatedProperties()
+ for entry in self._entries.values():
+ entry.SetCalculatedProperties()
+
+ def SetImagePos(self, image_pos):
+ Entry.SetImagePos(self, image_pos)
+ for entry in self._entries.values():
+ entry.SetImagePos(image_pos + self.offset)
def ProcessContents(self):
- self._section.ProcessEntryContents()
- super(Entry_section, self).ProcessContents()
+ sizes_ok_base = super(Entry_section, self).ProcessContents()
+ sizes_ok = True
+ for entry in self._entries.values():
+ if not entry.ProcessContents():
+ sizes_ok = False
+ return sizes_ok and sizes_ok_base
def CheckOffset(self):
- self._section.CheckEntries()
+ self.CheckEntries()
def WriteMap(self, fd, indent):
"""Write a map of the section to a .map file
@@ -97,11 +252,211 @@
Args:
fd: File to write the map to
"""
- self._section.WriteMap(fd, indent)
+ Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
+ self.size, self.image_pos)
+ for entry in self._entries.values():
+ entry.WriteMap(fd, indent + 1)
def GetEntries(self):
- return self._section.GetEntries()
+ return self._entries
+
+ def GetContentsByPhandle(self, phandle, source_entry):
+ """Get the data contents of an entry specified by a phandle
+
+ This uses a phandle to look up a node and and find the entry
+ associated with it. Then it returnst he contents of that entry.
+
+ Args:
+ phandle: Phandle to look up (integer)
+ source_entry: Entry containing that phandle (used for error
+ reporting)
+
+ Returns:
+ data from associated entry (as a string), or None if not found
+ """
+ node = self._node.GetFdt().LookupPhandle(phandle)
+ if not node:
+ source_entry.Raise("Cannot find node for phandle %d" % phandle)
+ for entry in self._entries.values():
+ if entry._node == node:
+ return entry.GetData()
+ source_entry.Raise("Cannot find entry for node '%s'" % node.name)
+
+ def LookupSymbol(self, sym_name, optional, msg):
+ """Look up a symbol in an ELF file
+
+ Looks up a symbol in an ELF file. Only entry types which come from an
+ ELF image can be used by this function.
+
+ At present the only entry property supported is offset.
+
+ Args:
+ sym_name: Symbol name in the ELF file to look up in the format
+ _binman_<entry>_prop_<property> where <entry> is the name of
+ the entry and <property> is the property to find (e.g.
+ _binman_u_boot_prop_offset). As a special case, you can append
+ _any to <entry> to have it search for any matching entry. E.g.
+ _binman_u_boot_any_prop_offset will match entries called u-boot,
+ u-boot-img and u-boot-nodtb)
+ optional: True if the symbol is optional. If False this function
+ will raise if the symbol is not found
+ msg: Message to display if an error occurs
+
+ Returns:
+ Value that should be assigned to that symbol, or None if it was
+ optional and not found
+
+ Raises:
+ ValueError if the symbol is invalid or not found, or references a
+ property which is not supported
+ """
+ m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
+ if not m:
+ raise ValueError("%s: Symbol '%s' has invalid format" %
+ (msg, sym_name))
+ entry_name, prop_name = m.groups()
+ entry_name = entry_name.replace('_', '-')
+ entry = self._entries.get(entry_name)
+ if not entry:
+ if entry_name.endswith('-any'):
+ root = entry_name[:-4]
+ for name in self._entries:
+ if name.startswith(root):
+ rest = name[len(root):]
+ if rest in ['', '-img', '-nodtb']:
+ entry = self._entries[name]
+ if not entry:
+ err = ("%s: Entry '%s' not found in list (%s)" %
+ (msg, entry_name, ','.join(self._entries.keys())))
+ if optional:
+ print('Warning: %s' % err, file=sys.stderr)
+ return None
+ raise ValueError(err)
+ if prop_name == 'offset':
+ return entry.offset
+ elif prop_name == 'image_pos':
+ return entry.image_pos
+ else:
+ raise ValueError("%s: No such property '%s'" % (msg, prop_name))
+
+ def GetRootSkipAtStart(self):
+ """Get the skip-at-start value for the top-level section
+
+ This is used to find out the starting offset for root section that
+ contains this section. If this is a top-level section then it returns
+ the skip-at-start offset for this section.
+
+ This is used to get the absolute position of section within the image.
+
+ Returns:
+ Integer skip-at-start value for the root section containing this
+ section
+ """
+ if self.section:
+ return self.section.GetRootSkipAtStart()
+ return self._skip_at_start
+
+ def GetStartOffset(self):
+ """Get the start offset for this section
+
+ Returns:
+ The first available offset in this section (typically 0)
+ """
+ return self._skip_at_start
+
+ def GetImageSize(self):
+ """Get the size of the image containing this section
+
+ Returns:
+ Image size as an integer number of bytes, which may be None if the
+ image size is dynamic and its sections have not yet been packed
+ """
+ return self.image.size
+
+ def FindEntryType(self, etype):
+ """Find an entry type in the section
+
+ Args:
+ etype: Entry type to find
+ Returns:
+ entry matching that type, or None if not found
+ """
+ for entry in self._entries.values():
+ if entry.etype == etype:
+ return entry
+ return None
+
+ def GetEntryContents(self):
+ """Call ObtainContents() for the section
+ """
+ todo = self._entries.values()
+ for passnum in range(3):
+ next_todo = []
+ for entry in todo:
+ if not entry.ObtainContents():
+ next_todo.append(entry)
+ todo = next_todo
+ if not todo:
+ break
+ if todo:
+ self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
+ todo)
+ return True
+
+ def _SetEntryOffsetSize(self, name, offset, size):
+ """Set the offset and size of an entry
+
+ Args:
+ name: Entry name to update
+ offset: New offset, or None to leave alone
+ size: New size, or None to leave alone
+ """
+ entry = self._entries.get(name)
+ if not entry:
+ self._Raise("Unable to set offset/size for unknown entry '%s'" %
+ name)
+ entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
+ size)
+
+ def GetEntryOffsets(self):
+ """Handle entries that want to set the offset/size of other entries
+
+ This calls each entry's GetOffsets() method. If it returns a list
+ of entries to update, it updates them.
+ """
+ for entry in self._entries.values():
+ offset_dict = entry.GetOffsets()
+ for name, info in offset_dict.items():
+ self._SetEntryOffsetSize(name, *info)
+
+
+ def CheckSize(self):
+ """Check that the image contents does not exceed its size, etc."""
+ contents_size = 0
+ for entry in self._entries.values():
+ contents_size = max(contents_size, entry.offset + entry.size)
+
+ contents_size -= self._skip_at_start
+
+ size = self.size
+ if not size:
+ size = self.pad_before + contents_size + self.pad_after
+ size = tools.Align(size, self.align_size)
+
+ if self.size and contents_size > self.size:
+ self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
+ (contents_size, contents_size, self.size, self.size))
+ if not self.size:
+ self.size = size
+ if self.size != tools.Align(self.size, self.align_size):
+ self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
+ (self.size, self.size, self.align_size,
+ self.align_size))
+ return size
- def ExpandToLimit(self, limit):
- super(Entry_section, self).ExpandToLimit(limit)
- self._section.ExpandSize(self.size)
+ def ListEntries(self, entries, indent):
+ """List the files in the section"""
+ Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
+ self.image_pos, None, self.offset, self)
+ for entry in self._entries.values():
+ entry.ListEntries(entries, indent + 1)
diff --git a/tools/binman/etype/text.py b/tools/binman/etype/text.py
index 9ee04d7..da1813a 100644
--- a/tools/binman/etype/text.py
+++ b/tools/binman/etype/text.py
@@ -22,6 +22,8 @@
that contains the string to place in the entry
<xxx> (actual name is the value of text-label): contains the string to
place in the entry.
+ <text>: The text to place in the entry (overrides the above mechanism).
+ This is useful when the text is constant.
Example node:
@@ -44,15 +46,28 @@
message = "a message directly in the node"
};
+ or just:
+
+ text {
+ size = <8>;
+ text = "some text directly in the node"
+ };
+
The text is not itself nul-terminated. This can be achieved, if required,
by setting the size of the entry to something larger than the text.
"""
def __init__(self, section, etype, node):
Entry.__init__(self, section, etype, node)
- label, = self.GetEntryArgsOrProps([EntryArg('text-label', str)])
- self.text_label = tools.ToStr(label) if type(label) != str else label
- value, = self.GetEntryArgsOrProps([EntryArg(self.text_label, str)])
- value = tools.ToBytes(value) if value is not None else value
+ value = fdt_util.GetString(self._node, 'text')
+ if value:
+ value = tools.ToBytes(value)
+ else:
+ label, = self.GetEntryArgsOrProps([EntryArg('text-label', str)])
+ self.text_label = label
+ if self.text_label:
+ value, = self.GetEntryArgsOrProps([EntryArg(self.text_label,
+ str)])
+ value = tools.ToBytes(value) if value is not None else value
self.value = value
def ObtainContents(self):
diff --git a/tools/binman/etype/u_boot_spl_elf.py b/tools/binman/etype/u_boot_spl_elf.py
index da328ae..24ee772 100644
--- a/tools/binman/etype/u_boot_spl_elf.py
+++ b/tools/binman/etype/u_boot_spl_elf.py
@@ -12,7 +12,7 @@
"""U-Boot SPL ELF image
Properties / Entry arguments:
- - filename: Filename of SPL u-boot (default 'spl/u-boot')
+ - filename: Filename of SPL u-boot (default 'spl/u-boot-spl')
This is the U-Boot SPL ELF image. It does not include a device tree but can
be relocated to any address for execution.
diff --git a/tools/binman/etype/u_boot_tpl_elf.py b/tools/binman/etype/u_boot_tpl_elf.py
new file mode 100644
index 0000000..9cc1cc2
--- /dev/null
+++ b/tools/binman/etype/u_boot_tpl_elf.py
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2018 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# Entry-type module for U-Boot TPL ELF image
+#
+
+from entry import Entry
+from blob import Entry_blob
+
+class Entry_u_boot_tpl_elf(Entry_blob):
+ """U-Boot TPL ELF image
+
+ Properties / Entry arguments:
+ - filename: Filename of TPL u-boot (default 'tpl/u-boot-tpl')
+
+ This is the U-Boot TPL ELF image. It does not include a device tree but can
+ be relocated to any address for execution.
+ """
+ def __init__(self, section, etype, node):
+ Entry_blob.__init__(self, section, etype, node)
+
+ def GetDefaultFilename(self):
+ return 'tpl/u-boot-tpl'
diff --git a/tools/binman/etype/u_boot_with_ucode_ptr.py b/tools/binman/etype/u_boot_with_ucode_ptr.py
index da0e124..cb7dbc6 100644
--- a/tools/binman/etype/u_boot_with_ucode_ptr.py
+++ b/tools/binman/etype/u_boot_with_ucode_ptr.py
@@ -49,7 +49,7 @@
def ProcessContents(self):
# If the image does not need microcode, there is nothing to do
if not self.target_offset:
- return
+ return True
# Get the offset of the microcode
ucode_entry = self.section.FindEntryType('u-boot-ucode')
@@ -91,6 +91,6 @@
# Write the microcode offset and size into the entry
offset_and_size = struct.pack('<2L', offset, size)
self.target_offset -= self.image_pos
- self.ProcessContentsUpdate(self.data[:self.target_offset] +
- offset_and_size +
- self.data[self.target_offset + 8:])
+ return self.ProcessContentsUpdate(self.data[:self.target_offset] +
+ offset_and_size +
+ self.data[self.target_offset + 8:])
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index cc57ef3..6a40d1f 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -6,6 +6,8 @@
#
# python -m unittest func_test.TestFunctional.testHelp
+from __future__ import print_function
+
import hashlib
from optparse import OptionParser
import os
@@ -16,14 +18,19 @@
import unittest
import binman
+import cbfs_util
import cmdline
import command
import control
import elf
import fdt
+from etype import fdtmap
+from etype import image_header
import fdt_util
import fmap_util
import test_util
+import gzip
+from image import Image
import state
import tools
import tout
@@ -59,9 +66,11 @@
VBLOCK_DATA = b'vblk'
FILES_DATA = (b"sorry I'm late\nOh, don't bother apologising, I'm " +
b"sorry you're alive\n")
-COMPRESS_DATA = b'data to compress'
+COMPRESS_DATA = b'compress xxxxxxxxxxxxxxxxxxxxxx data'
REFCODE_DATA = b'refcode'
+EXTRACT_DTB_SIZE = 0x3c9
+
class TestFunctional(unittest.TestCase):
"""Functional tests for binman
@@ -131,13 +140,46 @@
TestFunctional._MakeInputFile('compress', COMPRESS_DATA)
+ # Travis-CI may have an old lz4
+ self.have_lz4 = True
+ try:
+ tools.Run('lz4', '--no-frame-crc', '-c',
+ os.path.join(self._indir, 'u-boot.bin'))
+ except:
+ self.have_lz4 = False
+
@classmethod
def tearDownClass(self):
"""Remove the temporary input directory and its contents"""
- if self._indir:
- shutil.rmtree(self._indir)
+ if self.preserve_indir:
+ print('Preserving input dir: %s' % self._indir)
+ else:
+ if self._indir:
+ shutil.rmtree(self._indir)
self._indir = None
+ @classmethod
+ def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False,
+ toolpath=None, verbosity=None):
+ """Accept arguments controlling test execution
+
+ Args:
+ preserve_indir: Preserve the shared input directory used by all
+ tests in this class.
+ preserve_outdir: Preserve the output directories used by tests. Each
+ test has its own, so this is normally only useful when running a
+ single test.
+ toolpath: ist of paths to use for tools
+ """
+ cls.preserve_indir = preserve_indir
+ cls.preserve_outdirs = preserve_outdirs
+ cls.toolpath = toolpath
+ cls.verbosity = verbosity
+
+ def _CheckLz4(self):
+ if not self.have_lz4:
+ self.skipTest('lz4 --no-frame-crc not available')
+
def setUp(self):
# Enable this to turn on debugging output
# tout.Init(tout.DEBUG)
@@ -145,7 +187,10 @@
def tearDown(self):
"""Remove the temporary output directory"""
- tools._FinaliseForTest()
+ if self.preserve_outdirs:
+ print('Preserving output dir: %s' % tools.outdir)
+ else:
+ tools._FinaliseForTest()
@classmethod
def _ResetDtbs(self):
@@ -167,7 +212,7 @@
result.stdout + result.stderr))
return result
- def _DoBinman(self, *args):
+ def _DoBinman(self, *argv):
"""Run binman using directly (in the same process)
Args:
@@ -175,16 +220,14 @@
Returns:
Return value (0 for success)
"""
- args = list(args)
- if '-D' in sys.argv:
- args = args + ['-D']
- (options, args) = cmdline.ParseArgs(args)
- options.pager = 'binman-invalid-pager'
- options.build_dir = self._indir
+ argv = list(argv)
+ args = cmdline.ParseArgs(argv)
+ args.pager = 'binman-invalid-pager'
+ args.build_dir = self._indir
# For testing, you can force an increase in verbosity here
- # options.verbosity = tout.DEBUG
- return control.Binman(options, args)
+ # args.verbosity = tout.DEBUG
+ return control.Binman(args)
def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
entry_args=None, images=None, use_real_dtb=False,
@@ -202,17 +245,23 @@
value: value of that arg
images: List of image names to build
"""
- args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)]
+ args = []
if debug:
args.append('-D')
+ if verbosity is not None:
+ args.append('-v%d' % verbosity)
+ elif self.verbosity:
+ args.append('-v%d' % self.verbosity)
+ if self.toolpath:
+ for path in self.toolpath:
+ args += ['--toolpath', path]
+ args += ['build', '-p', '-I', self._indir, '-d', self.TestFile(fname)]
if map:
args.append('-m')
if update_dtb:
- args.append('-up')
+ args.append('-u')
if not use_real_dtb:
args.append('--fake-dtb')
- if verbosity is not None:
- args.append('-v%d' % verbosity)
if entry_args:
for arg, value in entry_args.items():
args.append('-a%s=%s' % (arg, value))
@@ -323,6 +372,17 @@
if reset_dtbs and use_real_dtb:
self._ResetDtbs()
+ def _DoReadFileRealDtb(self, fname):
+ """Run binman with a real .dtb file and return the resulting data
+
+ Args:
+ fname: DT source filename to use (e.g. 082_fdt_update_all.dts)
+
+ Returns:
+ Resulting image contents
+ """
+ return self._DoReadFileDtb(fname, use_real_dtb=True, update_dtb=True)[0]
+
def _DoReadFile(self, fname, use_real_dtb=False):
"""Helper function which discards the device-tree binary
@@ -419,16 +479,16 @@
"""
return struct.unpack('>L', dtb[4:8])[0]
- def _GetPropTree(self, dtb, prop_names):
+ def _GetPropTree(self, dtb, prop_names, prefix='/binman/'):
def AddNode(node, path):
if node.name != '/':
path += '/' + node.name
+ for prop in node.props.values():
+ if prop.name in prop_names:
+ prop_path = path + ':' + prop.name
+ tree[prop_path[len(prefix):]] = fdt_util.fdt32_to_cpu(
+ prop.value)
for subnode in node.subnodes:
- for prop in subnode.props.values():
- if prop.name in prop_names:
- prop_path = path + '/' + subnode.name + ':' + prop.name
- tree[prop_path[len('/binman/'):]] = fdt_util.fdt32_to_cpu(
- prop.value)
AddNode(subnode, path)
tree = {}
@@ -470,20 +530,20 @@
"""Test that we can run it with a specific board"""
self._SetupDtb('005_simple.dts', 'sandbox/u-boot.dtb')
TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
- result = self._DoBinman('-b', 'sandbox')
+ result = self._DoBinman('build', '-b', 'sandbox')
self.assertEqual(0, result)
def testNeedBoard(self):
"""Test that we get an error when no board ius supplied"""
with self.assertRaises(ValueError) as e:
- result = self._DoBinman()
+ result = self._DoBinman('build')
self.assertIn("Must provide a board to process (use -b <board>)",
str(e.exception))
def testMissingDt(self):
"""Test that an invalid device-tree file generates an error"""
with self.assertRaises(Exception) as e:
- self._RunBinman('-d', 'missing_file')
+ self._RunBinman('build', '-d', 'missing_file')
# We get one error from libfdt, and a different one from fdtget.
self.AssertInList(["Couldn't open blob from 'missing_file'",
'No such file or directory'], str(e.exception))
@@ -495,26 +555,26 @@
will come from the device-tree compiler (dtc).
"""
with self.assertRaises(Exception) as e:
- self._RunBinman('-d', self.TestFile('001_invalid.dts'))
+ self._RunBinman('build', '-d', self.TestFile('001_invalid.dts'))
self.assertIn("FATAL ERROR: Unable to parse input tree",
str(e.exception))
def testMissingNode(self):
"""Test that a device tree without a 'binman' node generates an error"""
with self.assertRaises(Exception) as e:
- self._DoBinman('-d', self.TestFile('002_missing_node.dts'))
+ self._DoBinman('build', '-d', self.TestFile('002_missing_node.dts'))
self.assertIn("does not have a 'binman' node", str(e.exception))
def testEmpty(self):
"""Test that an empty binman node works OK (i.e. does nothing)"""
- result = self._RunBinman('-d', self.TestFile('003_empty.dts'))
+ result = self._RunBinman('build', '-d', self.TestFile('003_empty.dts'))
self.assertEqual(0, len(result.stderr))
self.assertEqual(0, result.return_code)
def testInvalidEntry(self):
"""Test that an invalid entry is flagged"""
with self.assertRaises(Exception) as e:
- result = self._RunBinman('-d',
+ result = self._RunBinman('build', '-d',
self.TestFile('004_invalid_entry.dts'))
self.assertIn("Unknown entry type 'not-a-valid-type' in node "
"'/binman/not-a-valid-type'", str(e.exception))
@@ -526,7 +586,7 @@
def testSimpleDebug(self):
"""Test a simple binman run with debugging enabled"""
- data = self._DoTestFile('005_simple.dts', debug=True)
+ self._DoTestFile('005_simple.dts', debug=True)
def testDual(self):
"""Test that we can handle creating two images
@@ -537,7 +597,7 @@
self.assertEqual(0, retcode)
image = control.images['image1']
- self.assertEqual(len(U_BOOT_DATA), image._size)
+ self.assertEqual(len(U_BOOT_DATA), image.size)
fname = tools.GetOutputFilename('image1.bin')
self.assertTrue(os.path.exists(fname))
with open(fname, 'rb') as fd:
@@ -545,7 +605,7 @@
self.assertEqual(U_BOOT_DATA, data)
image = control.images['image2']
- self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
+ self.assertEqual(3 + len(U_BOOT_DATA) + 5, image.size)
fname = tools.GetOutputFilename('image2.bin')
self.assertTrue(os.path.exists(fname))
with open(fname, 'rb') as fd:
@@ -601,7 +661,7 @@
self.assertEqual(61, entry.offset)
self.assertEqual(len(U_BOOT_DATA), entry.size)
- self.assertEqual(65, image._size)
+ self.assertEqual(65, image.size)
def testPackExtra(self):
"""Test that extra packing feature works as expected"""
@@ -645,7 +705,7 @@
self.assertEqual(64, entry.size)
self.CheckNoGaps(entries)
- self.assertEqual(128, image._size)
+ self.assertEqual(128, image.size)
def testPackAlignPowerOf2(self):
"""Test that invalid entry alignment is detected"""
@@ -703,7 +763,7 @@
self.assertEqual(0, retcode)
self.assertIn('image', control.images)
image = control.images['image']
- self.assertEqual(7, image._size)
+ self.assertEqual(7, image.size)
def testPackImageSizeAlign(self):
"""Test that image size alignemnt works as expected"""
@@ -711,7 +771,7 @@
self.assertEqual(0, retcode)
self.assertIn('image', control.images)
image = control.images['image']
- self.assertEqual(16, image._size)
+ self.assertEqual(16, image.size)
def testPackInvalidImageAlign(self):
"""Test that invalid image alignment is detected"""
@@ -724,7 +784,7 @@
"""Test that invalid image alignment is detected"""
with self.assertRaises(ValueError) as e:
self._DoTestFile('020_pack_inv_image_align_power2.dts')
- self.assertIn("Section '/binman': Alignment size 131 must be a power of "
+ self.assertIn("Image '/binman': Alignment size 131 must be a power of "
"two", str(e.exception))
def testImagePadByte(self):
@@ -775,7 +835,7 @@
"""Test that the end-at-4gb property requires a size property"""
with self.assertRaises(ValueError) as e:
self._DoTestFile('027_pack_4gb_no_size.dts')
- self.assertIn("Section '/binman': Section size must be provided when "
+ self.assertIn("Image '/binman': Section size must be provided when "
"using end-at-4gb", str(e.exception))
def test4gbAndSkipAtStartTogether(self):
@@ -783,7 +843,7 @@
together"""
with self.assertRaises(ValueError) as e:
self._DoTestFile('80_4gb_and_skip_at_start_together.dts')
- self.assertIn("Section '/binman': Provide either 'end-at-4gb' or "
+ self.assertIn("Image '/binman': Provide either 'end-at-4gb' or "
"'skip-at-start'", str(e.exception))
def testPackX86RomOutside(self):
@@ -806,8 +866,8 @@
TestFunctional._MakeInputFile('descriptor.bin', b'')
with self.assertRaises(ValueError) as e:
self._DoTestFile('031_x86-rom-me.dts')
- self.assertIn("Node '/binman/intel-descriptor': Cannot find FD "
- "signature", str(e.exception))
+ self.assertIn("Node '/binman/intel-descriptor': Cannot find Intel Flash Descriptor (FD) signature",
+ str(e.exception))
def testPackX86RomBadDesc(self):
"""Test that the Intel requires a descriptor entry"""
@@ -820,6 +880,9 @@
def testPackX86RomMe(self):
"""Test that an x86 ROM with an ME region can be created"""
data = self._DoReadFile('031_x86-rom-me.dts')
+ expected_desc = tools.ReadFile(self.TestFile('descriptor.bin'))
+ if data[:0x1000] != expected_desc:
+ self.fail('Expected descriptor binary at start of image')
self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
def testPackVga(self):
@@ -1156,16 +1219,20 @@
"""Test that obtaining the contents works as expected"""
with self.assertRaises(ValueError) as e:
self._DoReadFile('057_unknown_contents.dts', True)
- self.assertIn("Section '/binman': Internal error: Could not complete "
+ self.assertIn("Image '/binman': Internal error: Could not complete "
"processing of contents: remaining [<_testing.Entry__testing ",
str(e.exception))
def testBadChangeSize(self):
"""Test that trying to change the size of an entry fails"""
- with self.assertRaises(ValueError) as e:
- self._DoReadFile('059_change_size.dts', True)
- self.assertIn("Node '/binman/_testing': Cannot update entry size from "
- '2 to 1', str(e.exception))
+ try:
+ state.SetAllowEntryExpansion(False)
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('059_change_size.dts', True)
+ self.assertIn("Node '/binman/_testing': Cannot update entry size from 1 to 2",
+ str(e.exception))
+ finally:
+ state.SetAllowEntryExpansion(True)
def testUpdateFdt(self):
"""Test that we can update the device tree with offset/size info"""
@@ -1242,7 +1309,8 @@
def testEntryArgsInvalidFormat(self):
"""Test that an invalid entry-argument format is detected"""
- args = ['-d', self.TestFile('064_entry_args_required.dts'), '-ano-value']
+ args = ['build', '-d', self.TestFile('064_entry_args_required.dts'),
+ '-ano-value']
with self.assertRaises(ValueError) as e:
self._DoBinman(*args)
self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
@@ -1286,7 +1354,7 @@
expected = (tools.ToBytes(TEXT_DATA) +
tools.GetBytes(0, 8 - len(TEXT_DATA)) +
tools.ToBytes(TEXT_DATA2) + tools.ToBytes(TEXT_DATA3) +
- b'some text')
+ b'some text' + b'more text')
self.assertEqual(expected, data)
def testEntryDocs(self):
@@ -1471,7 +1539,7 @@
expected = 'Skipping images: image1'
# We should only get the expected message in verbose mode
- for verbosity in (None, 2):
+ for verbosity in (0, 2):
with test_util.capture_sys_output() as (stdout, stderr):
retcode = self._DoTestFile('006_dual_image.dts',
verbosity=verbosity,
@@ -1487,8 +1555,7 @@
def testUpdateFdtAll(self):
"""Test that all device trees are updated with offset/size info"""
- data, _, _, _ = self._DoReadFileDtb('082_fdt_update_all.dts',
- use_real_dtb=True, update_dtb=True)
+ data = self._DoReadFileRealDtb('082_fdt_update_all.dts')
base_expected = {
'section:image-pos': 0,
@@ -1560,19 +1627,11 @@
self._ResetDtbs()
def _decompress(self, data):
- out = os.path.join(self._indir, 'lz4.tmp')
- with open(out, 'wb') as fd:
- fd.write(data)
- return tools.Run('lz4', '-dc', out, binary=True)
- '''
- try:
- orig = lz4.frame.decompress(data)
- except AttributeError:
- orig = lz4.decompress(data)
- '''
+ return tools.Decompress(data, 'lz4')
def testCompress(self):
"""Test compression of blobs"""
+ self._CheckLz4()
data, _, _, out_dtb_fname = self._DoReadFileDtb('083_compress.dts',
use_real_dtb=True, update_dtb=True)
dtb = fdt.Fdt(out_dtb_fname)
@@ -1594,12 +1653,13 @@
def testFilesCompress(self):
"""Test bringing in multiple files and compressing them"""
+ self._CheckLz4()
data = self._DoReadFile('085_files_compress.dts')
image = control.images['image']
entries = image.GetEntries()
files = entries['files']
- entries = files._section._entries
+ entries = files._entries
orig = b''
for i in range(1, 3):
@@ -1755,10 +1815,12 @@
"""Basic test of ELF entries"""
self._SetupSplElf()
with open(self.TestFile('bss_data'), 'rb') as fd:
+ TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
+ with open(self.TestFile('bss_data'), 'rb') as fd:
TestFunctional._MakeInputFile('-boot', fd.read())
data = self._DoReadFile('096_elf.dts')
- def testElfStripg(self):
+ def testElfStrip(self):
"""Basic test of ELF entries"""
self._SetupSplElf()
with open(self.TestFile('bss_data'), 'rb') as fd:
@@ -1784,7 +1846,7 @@
<none> 00000003 00000004 u-boot-align
''', map_data)
- def testPacRefCode(self):
+ def testPackRefCode(self):
"""Test that an image with an Intel Reference code binary works"""
data = self._DoReadFile('100_intel_refcode.dts')
self.assertEqual(REFCODE_DATA, data[:len(REFCODE_DATA)])
@@ -1810,6 +1872,853 @@
tools.GetBytes(0x26, 4) + U_BOOT_DATA +
tools.GetBytes(0x26, 8))
+ def testCbfsRaw(self):
+ """Test base handling of a Coreboot Filesystem (CBFS)
+
+ The exact contents of the CBFS is verified by similar tests in
+ cbfs_util_test.py. The tests here merely check that the files added to
+ the CBFS can be found in the final image.
+ """
+ data = self._DoReadFile('102_cbfs_raw.dts')
+ size = 0xb0
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(size, cbfs.rom_size)
+
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile = cbfs.files['u-boot-dtb']
+ self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
+
+ def testCbfsArch(self):
+ """Test on non-x86 architecture"""
+ data = self._DoReadFile('103_cbfs_raw_ppc.dts')
+ size = 0x100
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(size, cbfs.rom_size)
+
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile = cbfs.files['u-boot-dtb']
+ self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
+
+ def testCbfsStage(self):
+ """Tests handling of a Coreboot Filesystem (CBFS)"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+ elf_fname = os.path.join(self._indir, 'cbfs-stage.elf')
+ elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA)
+ size = 0xb0
+
+ data = self._DoReadFile('104_cbfs_stage.dts')
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(size, cbfs.rom_size)
+
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual(U_BOOT_DATA + U_BOOT_DTB_DATA, cfile.data)
+
+ def testCbfsRawCompress(self):
+ """Test handling of compressing raw files"""
+ self._CheckLz4()
+ data = self._DoReadFile('105_cbfs_raw_compress.dts')
+ size = 0x140
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual(COMPRESS_DATA, cfile.data)
+
+ def testCbfsBadArch(self):
+ """Test handling of a bad architecture"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('106_cbfs_bad_arch.dts')
+ self.assertIn("Invalid architecture 'bad-arch'", str(e.exception))
+
+ def testCbfsNoSize(self):
+ """Test handling of a missing size property"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('107_cbfs_no_size.dts')
+ self.assertIn('entry must have a size property', str(e.exception))
+
+ def testCbfsNoCOntents(self):
+ """Test handling of a CBFS entry which does not provide contentsy"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('108_cbfs_no_contents.dts')
+ self.assertIn('Could not complete processing of contents',
+ str(e.exception))
+
+ def testCbfsBadCompress(self):
+ """Test handling of a bad architecture"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('109_cbfs_bad_compress.dts')
+ self.assertIn("Invalid compression in 'u-boot': 'invalid-algo'",
+ str(e.exception))
+
+ def testCbfsNamedEntries(self):
+ """Test handling of named entries"""
+ data = self._DoReadFile('110_cbfs_name.dts')
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertIn('FRED', cbfs.files)
+ cfile1 = cbfs.files['FRED']
+ self.assertEqual(U_BOOT_DATA, cfile1.data)
+
+ self.assertIn('hello', cbfs.files)
+ cfile2 = cbfs.files['hello']
+ self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
+
+ def _SetupIfwi(self, fname):
+ """Set up to run an IFWI test
+
+ Args:
+ fname: Filename of input file to provide (fitimage.bin or ifwi.bin)
+ """
+ self._SetupSplElf()
+
+ # Intel Integrated Firmware Image (IFWI) file
+ with gzip.open(self.TestFile('%s.gz' % fname), 'rb') as fd:
+ data = fd.read()
+ TestFunctional._MakeInputFile(fname,data)
+
+ def _CheckIfwi(self, data):
+ """Check that an image with an IFWI contains the correct output
+
+ Args:
+ data: Conents of output file
+ """
+ expected_desc = tools.ReadFile(self.TestFile('descriptor.bin'))
+ if data[:0x1000] != expected_desc:
+ self.fail('Expected descriptor binary at start of image')
+
+ # We expect to find the TPL wil in subpart IBBP entry IBBL
+ image_fname = tools.GetOutputFilename('image.bin')
+ tpl_fname = tools.GetOutputFilename('tpl.out')
+ tools.RunIfwiTool(image_fname, tools.CMD_EXTRACT, fname=tpl_fname,
+ subpart='IBBP', entry_name='IBBL')
+
+ tpl_data = tools.ReadFile(tpl_fname)
+ self.assertEqual(tpl_data[:len(U_BOOT_TPL_DATA)], U_BOOT_TPL_DATA)
+
+ def testPackX86RomIfwi(self):
+ """Test that an x86 ROM with Integrated Firmware Image can be created"""
+ self._SetupIfwi('fitimage.bin')
+ data = self._DoReadFile('111_x86-rom-ifwi.dts')
+ self._CheckIfwi(data)
+
+ def testPackX86RomIfwiNoDesc(self):
+ """Test that an x86 ROM with IFWI can be created from an ifwi.bin file"""
+ self._SetupIfwi('ifwi.bin')
+ data = self._DoReadFile('112_x86-rom-ifwi-nodesc.dts')
+ self._CheckIfwi(data)
+
+ def testPackX86RomIfwiNoData(self):
+ """Test that an x86 ROM with IFWI handles missing data"""
+ self._SetupIfwi('ifwi.bin')
+ with self.assertRaises(ValueError) as e:
+ data = self._DoReadFile('113_x86-rom-ifwi-nodata.dts')
+ self.assertIn('Could not complete processing of contents',
+ str(e.exception))
+
+ def testCbfsOffset(self):
+ """Test a CBFS with files at particular offsets
+
+ Like all CFBS tests, this is just checking the logic that calls
+ cbfs_util. See cbfs_util_test for fully tests (e.g. test_cbfs_offset()).
+ """
+ data = self._DoReadFile('114_cbfs_offset.dts')
+ size = 0x200
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(size, cbfs.rom_size)
+
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual(U_BOOT_DATA, cfile.data)
+ self.assertEqual(0x40, cfile.cbfs_offset)
+
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile2 = cbfs.files['u-boot-dtb']
+ self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
+ self.assertEqual(0x140, cfile2.cbfs_offset)
+
+ def testFdtmap(self):
+ """Test an FDT map can be inserted in the image"""
+ data = self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
+ fdtmap_data = data[len(U_BOOT_DATA):]
+ magic = fdtmap_data[:8]
+ self.assertEqual('_FDTMAP_', magic)
+ self.assertEqual(tools.GetBytes(0, 8), fdtmap_data[8:16])
+
+ fdt_data = fdtmap_data[16:]
+ dtb = fdt.Fdt.FromData(fdt_data)
+ dtb.Scan()
+ props = self._GetPropTree(dtb, ['offset', 'size', 'image-pos'],
+ prefix='/')
+ self.assertEqual({
+ 'image-pos': 0,
+ 'offset': 0,
+ 'u-boot:offset': 0,
+ 'u-boot:size': len(U_BOOT_DATA),
+ 'u-boot:image-pos': 0,
+ 'fdtmap:image-pos': 4,
+ 'fdtmap:offset': 4,
+ 'fdtmap:size': len(fdtmap_data),
+ 'size': len(data),
+ }, props)
+
+ def testFdtmapNoMatch(self):
+ """Check handling of an FDT map when the section cannot be found"""
+ self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
+
+ # Mangle the section name, which should cause a mismatch between the
+ # correct FDT path and the one expected by the section
+ image = control.images['image']
+ image._node.path += '-suffix'
+ entries = image.GetEntries()
+ fdtmap = entries['fdtmap']
+ with self.assertRaises(ValueError) as e:
+ fdtmap._GetFdtmap()
+ self.assertIn("Cannot locate node for path '/binman-suffix'",
+ str(e.exception))
+
+ def testFdtmapHeader(self):
+ """Test an FDT map and image header can be inserted in the image"""
+ data = self.data = self._DoReadFileRealDtb('116_fdtmap_hdr.dts')
+ fdtmap_pos = len(U_BOOT_DATA)
+ fdtmap_data = data[fdtmap_pos:]
+ fdt_data = fdtmap_data[16:]
+ dtb = fdt.Fdt.FromData(fdt_data)
+ fdt_size = dtb.GetFdtObj().totalsize()
+ hdr_data = data[-8:]
+ self.assertEqual('BinM', hdr_data[:4])
+ offset = struct.unpack('<I', hdr_data[4:])[0] & 0xffffffff
+ self.assertEqual(fdtmap_pos - 0x400, offset - (1 << 32))
+
+ def testFdtmapHeaderStart(self):
+ """Test an image header can be inserted at the image start"""
+ data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
+ fdtmap_pos = 0x100 + len(U_BOOT_DATA)
+ hdr_data = data[:8]
+ self.assertEqual('BinM', hdr_data[:4])
+ offset = struct.unpack('<I', hdr_data[4:])[0]
+ self.assertEqual(fdtmap_pos, offset)
+
+ def testFdtmapHeaderPos(self):
+ """Test an image header can be inserted at a chosen position"""
+ data = self.data = self._DoReadFileRealDtb('118_fdtmap_hdr_pos.dts')
+ fdtmap_pos = 0x100 + len(U_BOOT_DATA)
+ hdr_data = data[0x80:0x88]
+ self.assertEqual('BinM', hdr_data[:4])
+ offset = struct.unpack('<I', hdr_data[4:])[0]
+ self.assertEqual(fdtmap_pos, offset)
+
+ def testHeaderMissingFdtmap(self):
+ """Test an image header requires an fdtmap"""
+ with self.assertRaises(ValueError) as e:
+ self.data = self._DoReadFileRealDtb('119_fdtmap_hdr_missing.dts')
+ self.assertIn("'image_header' section must have an 'fdtmap' sibling",
+ str(e.exception))
+
+ def testHeaderNoLocation(self):
+ """Test an image header with a no specified location is detected"""
+ with self.assertRaises(ValueError) as e:
+ self.data = self._DoReadFileRealDtb('120_hdr_no_location.dts')
+ self.assertIn("Invalid location 'None', expected 'start' or 'end'",
+ str(e.exception))
+
+ def testEntryExpand(self):
+ """Test expanding an entry after it is packed"""
+ data = self._DoReadFile('121_entry_expand.dts')
+ self.assertEqual(b'aa', data[:2])
+ self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
+ self.assertEqual(b'aa', data[-2:])
+
+ def testEntryExpandBad(self):
+ """Test expanding an entry after it is packed, twice"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('122_entry_expand_twice.dts')
+ self.assertIn("Image '/binman': Entries expanded after packing",
+ str(e.exception))
+
+ def testEntryExpandSection(self):
+ """Test expanding an entry within a section after it is packed"""
+ data = self._DoReadFile('123_entry_expand_section.dts')
+ self.assertEqual(b'aa', data[:2])
+ self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
+ self.assertEqual(b'aa', data[-2:])
+
+ def testCompressDtb(self):
+ """Test that compress of device-tree files is supported"""
+ self._CheckLz4()
+ data = self.data = self._DoReadFileRealDtb('124_compress_dtb.dts')
+ self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
+ comp_data = data[len(U_BOOT_DATA):]
+ orig = self._decompress(comp_data)
+ dtb = fdt.Fdt.FromData(orig)
+ dtb.Scan()
+ props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
+ expected = {
+ 'u-boot:size': len(U_BOOT_DATA),
+ 'u-boot-dtb:uncomp-size': len(orig),
+ 'u-boot-dtb:size': len(comp_data),
+ 'size': len(data),
+ }
+ self.assertEqual(expected, props)
+
+ def testCbfsUpdateFdt(self):
+ """Test that we can update the device tree with CBFS offset/size info"""
+ self._CheckLz4()
+ data, _, _, out_dtb_fname = self._DoReadFileDtb('125_cbfs_update.dts',
+ update_dtb=True)
+ dtb = fdt.Fdt(out_dtb_fname)
+ dtb.Scan()
+ props = self._GetPropTree(dtb, ['offset', 'size', 'image-pos',
+ 'uncomp-size'])
+ del props['cbfs/u-boot:size']
+ self.assertEqual({
+ 'offset': 0,
+ 'size': len(data),
+ 'image-pos': 0,
+ 'cbfs:offset': 0,
+ 'cbfs:size': len(data),
+ 'cbfs:image-pos': 0,
+ 'cbfs/u-boot:offset': 0x38,
+ 'cbfs/u-boot:uncomp-size': len(U_BOOT_DATA),
+ 'cbfs/u-boot:image-pos': 0x38,
+ 'cbfs/u-boot-dtb:offset': 0xb8,
+ 'cbfs/u-boot-dtb:size': len(U_BOOT_DATA),
+ 'cbfs/u-boot-dtb:image-pos': 0xb8,
+ }, props)
+
+ def testCbfsBadType(self):
+ """Test an image header with a no specified location is detected"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('126_cbfs_bad_type.dts')
+ self.assertIn("Unknown cbfs-type 'badtype'", str(e.exception))
+
+ def testList(self):
+ """Test listing the files in an image"""
+ self._CheckLz4()
+ data = self._DoReadFile('127_list.dts')
+ image = control.images['image']
+ entries = image.BuildEntryList()
+ self.assertEqual(7, len(entries))
+
+ ent = entries[0]
+ self.assertEqual(0, ent.indent)
+ self.assertEqual('main-section', ent.name)
+ self.assertEqual('section', ent.etype)
+ self.assertEqual(len(data), ent.size)
+ self.assertEqual(0, ent.image_pos)
+ self.assertEqual(None, ent.uncomp_size)
+ self.assertEqual(0, ent.offset)
+
+ ent = entries[1]
+ self.assertEqual(1, ent.indent)
+ self.assertEqual('u-boot', ent.name)
+ self.assertEqual('u-boot', ent.etype)
+ self.assertEqual(len(U_BOOT_DATA), ent.size)
+ self.assertEqual(0, ent.image_pos)
+ self.assertEqual(None, ent.uncomp_size)
+ self.assertEqual(0, ent.offset)
+
+ ent = entries[2]
+ self.assertEqual(1, ent.indent)
+ self.assertEqual('section', ent.name)
+ self.assertEqual('section', ent.etype)
+ section_size = ent.size
+ self.assertEqual(0x100, ent.image_pos)
+ self.assertEqual(None, ent.uncomp_size)
+ self.assertEqual(0x100, ent.offset)
+
+ ent = entries[3]
+ self.assertEqual(2, ent.indent)
+ self.assertEqual('cbfs', ent.name)
+ self.assertEqual('cbfs', ent.etype)
+ self.assertEqual(0x400, ent.size)
+ self.assertEqual(0x100, ent.image_pos)
+ self.assertEqual(None, ent.uncomp_size)
+ self.assertEqual(0, ent.offset)
+
+ ent = entries[4]
+ self.assertEqual(3, ent.indent)
+ self.assertEqual('u-boot', ent.name)
+ self.assertEqual('u-boot', ent.etype)
+ self.assertEqual(len(U_BOOT_DATA), ent.size)
+ self.assertEqual(0x138, ent.image_pos)
+ self.assertEqual(None, ent.uncomp_size)
+ self.assertEqual(0x38, ent.offset)
+
+ ent = entries[5]
+ self.assertEqual(3, ent.indent)
+ self.assertEqual('u-boot-dtb', ent.name)
+ self.assertEqual('text', ent.etype)
+ self.assertGreater(len(COMPRESS_DATA), ent.size)
+ self.assertEqual(0x178, ent.image_pos)
+ self.assertEqual(len(COMPRESS_DATA), ent.uncomp_size)
+ self.assertEqual(0x78, ent.offset)
+
+ ent = entries[6]
+ self.assertEqual(2, ent.indent)
+ self.assertEqual('u-boot-dtb', ent.name)
+ self.assertEqual('u-boot-dtb', ent.etype)
+ self.assertEqual(0x500, ent.image_pos)
+ self.assertEqual(len(U_BOOT_DTB_DATA), ent.uncomp_size)
+ dtb_size = ent.size
+ # Compressing this data expands it since headers are added
+ self.assertGreater(dtb_size, len(U_BOOT_DTB_DATA))
+ self.assertEqual(0x400, ent.offset)
+
+ self.assertEqual(len(data), 0x100 + section_size)
+ self.assertEqual(section_size, 0x400 + dtb_size)
+
+ def testFindFdtmap(self):
+ """Test locating an FDT map in an image"""
+ self._CheckLz4()
+ data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
+ image = control.images['image']
+ entries = image.GetEntries()
+ entry = entries['fdtmap']
+ self.assertEqual(entry.image_pos, fdtmap.LocateFdtmap(data))
+
+ def testFindFdtmapMissing(self):
+ """Test failing to locate an FDP map"""
+ data = self._DoReadFile('005_simple.dts')
+ self.assertEqual(None, fdtmap.LocateFdtmap(data))
+
+ def testFindImageHeader(self):
+ """Test locating a image header"""
+ self._CheckLz4()
+ data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
+ image = control.images['image']
+ entries = image.GetEntries()
+ entry = entries['fdtmap']
+ # The header should point to the FDT map
+ self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
+
+ def testFindImageHeaderStart(self):
+ """Test locating a image header located at the start of an image"""
+ data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
+ image = control.images['image']
+ entries = image.GetEntries()
+ entry = entries['fdtmap']
+ # The header should point to the FDT map
+ self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
+
+ def testFindImageHeaderMissing(self):
+ """Test failing to locate an image header"""
+ data = self._DoReadFile('005_simple.dts')
+ self.assertEqual(None, image_header.LocateHeaderOffset(data))
+
+ def testReadImage(self):
+ """Test reading an image and accessing its FDT map"""
+ self._CheckLz4()
+ data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ orig_image = control.images['image']
+ image = Image.FromFile(image_fname)
+ self.assertEqual(orig_image.GetEntries().keys(),
+ image.GetEntries().keys())
+
+ orig_entry = orig_image.GetEntries()['fdtmap']
+ entry = image.GetEntries()['fdtmap']
+ self.assertEquals(orig_entry.offset, entry.offset)
+ self.assertEquals(orig_entry.size, entry.size)
+ self.assertEquals(orig_entry.image_pos, entry.image_pos)
+
+ def testReadImageNoHeader(self):
+ """Test accessing an image's FDT map without an image header"""
+ self._CheckLz4()
+ data = self._DoReadFileRealDtb('129_decode_image_nohdr.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ image = Image.FromFile(image_fname)
+ self.assertTrue(isinstance(image, Image))
+ self.assertEqual('image', image.image_name)
+
+ def testReadImageFail(self):
+ """Test failing to read an image image's FDT map"""
+ self._DoReadFile('005_simple.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ with self.assertRaises(ValueError) as e:
+ image = Image.FromFile(image_fname)
+ self.assertIn("Cannot find FDT map in image", str(e.exception))
+
+ def testListCmd(self):
+ """Test listing the files in an image using an Fdtmap"""
+ self._CheckLz4()
+ data = self._DoReadFileRealDtb('130_list_fdtmap.dts')
+
+ # lz4 compression size differs depending on the version
+ image = control.images['image']
+ entries = image.GetEntries()
+ section_size = entries['section'].size
+ fdt_size = entries['section'].GetEntries()['u-boot-dtb'].size
+ fdtmap_offset = entries['fdtmap'].offset
+
+ image_fname = tools.GetOutputFilename('image.bin')
+ with test_util.capture_sys_output() as (stdout, stderr):
+ self._DoBinman('ls', '-i', image_fname)
+ lines = stdout.getvalue().splitlines()
+ expected = [
+'Name Image-pos Size Entry-type Offset Uncomp-size',
+'----------------------------------------------------------------------',
+'main-section 0 c00 section 0',
+' u-boot 0 4 u-boot 0',
+' section 100 %x section 100' % section_size,
+' cbfs 100 400 cbfs 0',
+' u-boot 138 4 u-boot 38',
+' u-boot-dtb 180 10f u-boot-dtb 80 3c9',
+' u-boot-dtb 500 %x u-boot-dtb 400 3c9' % fdt_size,
+' fdtmap %x 395 fdtmap %x' %
+ (fdtmap_offset, fdtmap_offset),
+' image-header bf8 8 image-header bf8',
+ ]
+ self.assertEqual(expected, lines)
+
+ def testListCmdFail(self):
+ """Test failing to list an image"""
+ self._DoReadFile('005_simple.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ with self.assertRaises(ValueError) as e:
+ self._DoBinman('ls', '-i', image_fname)
+ self.assertIn("Cannot find FDT map in image", str(e.exception))
+
+ def _RunListCmd(self, paths, expected):
+ """List out entries and check the result
+
+ Args:
+ paths: List of paths to pass to the list command
+ expected: Expected list of filenames to be returned, in order
+ """
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ image = Image.FromFile(image_fname)
+ lines = image.GetListEntries(paths)[1]
+ files = [line[0].strip() for line in lines[1:]]
+ self.assertEqual(expected, files)
+
+ def testListCmdSection(self):
+ """Test listing the files in a section"""
+ self._RunListCmd(['section'],
+ ['section', 'cbfs', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
+
+ def testListCmdFile(self):
+ """Test listing a particular file"""
+ self._RunListCmd(['*u-boot-dtb'], ['u-boot-dtb', 'u-boot-dtb'])
+
+ def testListCmdWildcard(self):
+ """Test listing a wildcarded file"""
+ self._RunListCmd(['*boot*'],
+ ['u-boot', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
+
+ def testListCmdWildcardMulti(self):
+ """Test listing a wildcarded file"""
+ self._RunListCmd(['*cb*', '*head*'],
+ ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
+
+ def testListCmdEmpty(self):
+ """Test listing a wildcarded file"""
+ self._RunListCmd(['nothing'], [])
+
+ def testListCmdPath(self):
+ """Test listing the files in a sub-entry of a section"""
+ self._RunListCmd(['section/cbfs'], ['cbfs', 'u-boot', 'u-boot-dtb'])
+
+ def _RunExtractCmd(self, entry_name, decomp=True):
+ """Extract an entry from an image
+
+ Args:
+ entry_name: Entry name to extract
+ decomp: True to decompress the data if compressed, False to leave
+ it in its raw uncompressed format
+
+ Returns:
+ data from entry
+ """
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ return control.ReadEntry(image_fname, entry_name, decomp)
+
+ def testExtractSimple(self):
+ """Test extracting a single file"""
+ data = self._RunExtractCmd('u-boot')
+ self.assertEqual(U_BOOT_DATA, data)
+
+ def testExtractSection(self):
+ """Test extracting the files in a section"""
+ data = self._RunExtractCmd('section')
+ cbfs_data = data[:0x400]
+ cbfs = cbfs_util.CbfsReader(cbfs_data)
+ self.assertEqual(['u-boot', 'u-boot-dtb', ''], cbfs.files.keys())
+ dtb_data = data[0x400:]
+ dtb = self._decompress(dtb_data)
+ self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
+
+ def testExtractCompressed(self):
+ """Test extracting compressed data"""
+ data = self._RunExtractCmd('section/u-boot-dtb')
+ self.assertEqual(EXTRACT_DTB_SIZE, len(data))
+
+ def testExtractRaw(self):
+ """Test extracting compressed data without decompressing it"""
+ data = self._RunExtractCmd('section/u-boot-dtb', decomp=False)
+ dtb = self._decompress(data)
+ self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
+
+ def testExtractCbfs(self):
+ """Test extracting CBFS data"""
+ data = self._RunExtractCmd('section/cbfs/u-boot')
+ self.assertEqual(U_BOOT_DATA, data)
+
+ def testExtractCbfsCompressed(self):
+ """Test extracting CBFS compressed data"""
+ data = self._RunExtractCmd('section/cbfs/u-boot-dtb')
+ self.assertEqual(EXTRACT_DTB_SIZE, len(data))
+
+ def testExtractCbfsRaw(self):
+ """Test extracting CBFS compressed data without decompressing it"""
+ data = self._RunExtractCmd('section/cbfs/u-boot-dtb', decomp=False)
+ dtb = tools.Decompress(data, 'lzma')
+ self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
+
+ def testExtractBadEntry(self):
+ """Test extracting a bad section path"""
+ with self.assertRaises(ValueError) as e:
+ self._RunExtractCmd('section/does-not-exist')
+ self.assertIn("Entry 'does-not-exist' not found in '/section'",
+ str(e.exception))
+
+ def testExtractMissingFile(self):
+ """Test extracting file that does not exist"""
+ with self.assertRaises(IOError) as e:
+ control.ReadEntry('missing-file', 'name')
+
+ def testExtractBadFile(self):
+ """Test extracting an invalid file"""
+ fname = os.path.join(self._indir, 'badfile')
+ tools.WriteFile(fname, b'')
+ with self.assertRaises(ValueError) as e:
+ control.ReadEntry(fname, 'name')
+
+ def testExtractCmd(self):
+ """Test extracting a file fron an image on the command line"""
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ fname = os.path.join(self._indir, 'output.extact')
+ with test_util.capture_sys_output() as (stdout, stderr):
+ self._DoBinman('extract', '-i', image_fname, 'u-boot', '-f', fname)
+ data = tools.ReadFile(fname)
+ self.assertEqual(U_BOOT_DATA, data)
+
+ def testExtractOneEntry(self):
+ """Test extracting a single entry fron an image """
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ fname = os.path.join(self._indir, 'output.extact')
+ control.ExtractEntries(image_fname, fname, None, ['u-boot'])
+ data = tools.ReadFile(fname)
+ self.assertEqual(U_BOOT_DATA, data)
+
+ def _CheckExtractOutput(self, decomp):
+ """Helper to test file output with and without decompression
+
+ Args:
+ decomp: True to decompress entry data, False to output it raw
+ """
+ def _CheckPresent(entry_path, expect_data, expect_size=None):
+ """Check and remove expected file
+
+ This checks the data/size of a file and removes the file both from
+ the outfiles set and from the output directory. Once all files are
+ processed, both the set and directory should be empty.
+
+ Args:
+ entry_path: Entry path
+ expect_data: Data to expect in file, or None to skip check
+ expect_size: Size of data to expect in file, or None to skip
+ """
+ path = os.path.join(outdir, entry_path)
+ data = tools.ReadFile(path)
+ os.remove(path)
+ if expect_data:
+ self.assertEqual(expect_data, data)
+ elif expect_size:
+ self.assertEqual(expect_size, len(data))
+ outfiles.remove(path)
+
+ def _CheckDirPresent(name):
+ """Remove expected directory
+
+ This gives an error if the directory does not exist as expected
+
+ Args:
+ name: Name of directory to remove
+ """
+ path = os.path.join(outdir, name)
+ os.rmdir(path)
+
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ outdir = os.path.join(self._indir, 'extract')
+ einfos = control.ExtractEntries(image_fname, None, outdir, [], decomp)
+
+ # Create a set of all file that were output (should be 9)
+ outfiles = set()
+ for root, dirs, files in os.walk(outdir):
+ outfiles |= set([os.path.join(root, fname) for fname in files])
+ self.assertEqual(9, len(outfiles))
+ self.assertEqual(9, len(einfos))
+
+ image = control.images['image']
+ entries = image.GetEntries()
+
+ # Check the 9 files in various ways
+ section = entries['section']
+ section_entries = section.GetEntries()
+ cbfs_entries = section_entries['cbfs'].GetEntries()
+ _CheckPresent('u-boot', U_BOOT_DATA)
+ _CheckPresent('section/cbfs/u-boot', U_BOOT_DATA)
+ dtb_len = EXTRACT_DTB_SIZE
+ if not decomp:
+ dtb_len = cbfs_entries['u-boot-dtb'].size
+ _CheckPresent('section/cbfs/u-boot-dtb', None, dtb_len)
+ if not decomp:
+ dtb_len = section_entries['u-boot-dtb'].size
+ _CheckPresent('section/u-boot-dtb', None, dtb_len)
+
+ fdtmap = entries['fdtmap']
+ _CheckPresent('fdtmap', fdtmap.data)
+ hdr = entries['image-header']
+ _CheckPresent('image-header', hdr.data)
+
+ _CheckPresent('section/root', section.data)
+ cbfs = section_entries['cbfs']
+ _CheckPresent('section/cbfs/root', cbfs.data)
+ data = tools.ReadFile(image_fname)
+ _CheckPresent('root', data)
+
+ # There should be no files left. Remove all the directories to check.
+ # If there are any files/dirs remaining, one of these checks will fail.
+ self.assertEqual(0, len(outfiles))
+ _CheckDirPresent('section/cbfs')
+ _CheckDirPresent('section')
+ _CheckDirPresent('')
+ self.assertFalse(os.path.exists(outdir))
+
+ def testExtractAllEntries(self):
+ """Test extracting all entries"""
+ self._CheckLz4()
+ self._CheckExtractOutput(decomp=True)
+
+ def testExtractAllEntriesRaw(self):
+ """Test extracting all entries without decompressing them"""
+ self._CheckLz4()
+ self._CheckExtractOutput(decomp=False)
+
+ def testExtractSelectedEntries(self):
+ """Test extracting some entries"""
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ outdir = os.path.join(self._indir, 'extract')
+ einfos = control.ExtractEntries(image_fname, None, outdir,
+ ['*cb*', '*head*'])
+
+ # File output is tested by testExtractAllEntries(), so just check that
+ # the expected entries are selected
+ names = [einfo.name for einfo in einfos]
+ self.assertEqual(names,
+ ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
+
+ def testExtractNoEntryPaths(self):
+ """Test extracting some entries"""
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ with self.assertRaises(ValueError) as e:
+ control.ExtractEntries(image_fname, 'fname', None, [])
+ self.assertIn('Must specify an entry path to write with -o',
+ str(e.exception))
+
+ def testExtractTooManyEntryPaths(self):
+ """Test extracting some entries"""
+ self._CheckLz4()
+ self._DoReadFileRealDtb('130_list_fdtmap.dts')
+ image_fname = tools.GetOutputFilename('image.bin')
+ with self.assertRaises(ValueError) as e:
+ control.ExtractEntries(image_fname, 'fname', None, ['a', 'b'])
+ self.assertIn('Must specify exactly one entry path to write with -o',
+ str(e.exception))
+
+ def testPackAlignSection(self):
+ """Test that sections can have alignment"""
+ self._DoReadFile('131_pack_align_section.dts')
+
+ self.assertIn('image', control.images)
+ image = control.images['image']
+ entries = image.GetEntries()
+ self.assertEqual(3, len(entries))
+
+ # First u-boot
+ self.assertIn('u-boot', entries)
+ entry = entries['u-boot']
+ self.assertEqual(0, entry.offset)
+ self.assertEqual(0, entry.image_pos)
+ self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
+ self.assertEqual(len(U_BOOT_DATA), entry.size)
+
+ # Section0
+ self.assertIn('section0', entries)
+ section0 = entries['section0']
+ self.assertEqual(0x10, section0.offset)
+ self.assertEqual(0x10, section0.image_pos)
+ self.assertEqual(len(U_BOOT_DATA), section0.size)
+
+ # Second u-boot
+ section_entries = section0.GetEntries()
+ self.assertIn('u-boot', section_entries)
+ entry = section_entries['u-boot']
+ self.assertEqual(0, entry.offset)
+ self.assertEqual(0x10, entry.image_pos)
+ self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
+ self.assertEqual(len(U_BOOT_DATA), entry.size)
+
+ # Section1
+ self.assertIn('section1', entries)
+ section1 = entries['section1']
+ self.assertEqual(0x14, section1.offset)
+ self.assertEqual(0x14, section1.image_pos)
+ self.assertEqual(0x20, section1.size)
+
+ # Second u-boot
+ section_entries = section1.GetEntries()
+ self.assertIn('u-boot', section_entries)
+ entry = section_entries['u-boot']
+ self.assertEqual(0, entry.offset)
+ self.assertEqual(0x14, entry.image_pos)
+ self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
+ self.assertEqual(len(U_BOOT_DATA), entry.size)
+
+ # Section2
+ self.assertIn('section2', section_entries)
+ section2 = section_entries['section2']
+ self.assertEqual(0x4, section2.offset)
+ self.assertEqual(0x18, section2.image_pos)
+ self.assertEqual(4, section2.size)
+
+ # Third u-boot
+ section_entries = section2.GetEntries()
+ self.assertIn('u-boot', section_entries)
+ entry = section_entries['u-boot']
+ self.assertEqual(0, entry.offset)
+ self.assertEqual(0x18, entry.image_pos)
+ self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
+ self.assertEqual(len(U_BOOT_DATA), entry.size)
+
if __name__ == "__main__":
unittest.main()
diff --git a/tools/binman/image.py b/tools/binman/image.py
index f237ae3..fb6e591 100644
--- a/tools/binman/image.py
+++ b/tools/binman/image.py
@@ -8,15 +8,21 @@
from __future__ import print_function
from collections import OrderedDict
+import fnmatch
from operator import attrgetter
import re
import sys
+from entry import Entry
+from etype import fdtmap
+from etype import image_header
+from etype import section
+import fdt
import fdt_util
-import bsection
import tools
+import tout
-class Image:
+class Image(section.Entry_section):
"""A Image, representing an output from binman
An image is comprised of a collection of entries each containing binary
@@ -24,12 +30,8 @@
This class implements the various operations needed for images.
- Atrtributes:
- _node: Node object that contains the image definition in device tree
- _name: Image name
- _size: Image size in bytes, or None if not known yet
- _filename: Output filename for image
- _sections: Sections present in this image (may be one or more)
+ Attributes:
+ filename: Output filename for image
Args:
test: True if this is being called from a test of Images. This this case
@@ -37,106 +39,94 @@
we create a section manually.
"""
def __init__(self, name, node, test=False):
- self._node = node
- self._name = name
- self._size = None
- self._filename = '%s.bin' % self._name
- if test:
- self._section = bsection.Section('main-section', None, self._node,
- self, True)
- else:
- self._ReadNode()
+ self.image = self
+ section.Entry_section.__init__(self, None, 'section', node, test)
+ self.name = 'main-section'
+ self.image_name = name
+ self._filename = '%s.bin' % self.image_name
+ if not test:
+ filename = fdt_util.GetString(self._node, 'filename')
+ if filename:
+ self._filename = filename
- def _ReadNode(self):
- """Read properties from the image node"""
- self._size = fdt_util.GetInt(self._node, 'size')
- filename = fdt_util.GetString(self._node, 'filename')
- if filename:
- self._filename = filename
- self._section = bsection.Section('main-section', None, self._node, self)
+ @classmethod
+ def FromFile(cls, fname):
+ """Convert an image file into an Image for use in binman
- def GetFdtSet(self):
- """Get the set of device tree files used by this image"""
- return self._section.GetFdtSet()
+ Args:
+ fname: Filename of image file to read
- def ExpandEntries(self):
- """Expand out any entries which have calculated sub-entries
-
- Some entries are expanded out at runtime, e.g. 'files', which produces
- a section containing a list of files. Process these entries so that
- this information is added to the device tree.
- """
- self._section.ExpandEntries()
-
- def AddMissingProperties(self):
- """Add properties that are not present in the device tree
+ Returns:
+ Image object on success
- When binman has completed packing the entries the offset and size of
- each entry are known. But before this the device tree may not specify
- these. Add any missing properties, with a dummy value, so that the
- size of the entry is correct. That way we can insert the correct values
- later.
+ Raises:
+ ValueError if something goes wrong
"""
- self._section.AddMissingProperties()
+ data = tools.ReadFile(fname)
+ size = len(data)
- def ProcessFdt(self, fdt):
- """Allow entries to adjust the device tree
-
- Some entries need to adjust the device tree for their purposes. This
- may involve adding or deleting properties.
- """
- return self._section.ProcessFdt(fdt)
+ # First look for an image header
+ pos = image_header.LocateHeaderOffset(data)
+ if pos is None:
+ # Look for the FDT map
+ pos = fdtmap.LocateFdtmap(data)
+ if pos is None:
+ raise ValueError('Cannot find FDT map in image')
- def GetEntryContents(self):
- """Call ObtainContents() for the section
- """
- self._section.GetEntryContents()
+ # We don't know the FDT size, so check its header first
+ probe_dtb = fdt.Fdt.FromData(
+ data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
+ dtb_size = probe_dtb.GetFdtObj().totalsize()
+ fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
+ dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
+ dtb.Scan()
- def GetEntryOffsets(self):
- """Handle entries that want to set the offset/size of other entries
+ # Return an Image with the associated nodes
+ image = Image('image', dtb.GetRoot())
+ image._data = data
+ return image
- This calls each entry's GetOffsets() method. If it returns a list
- of entries to update, it updates them.
- """
- self._section.GetEntryOffsets()
+ def Raise(self, msg):
+ """Convenience function to raise an error referencing an image"""
+ raise ValueError("Image '%s': %s" % (self._node.path, msg))
def PackEntries(self):
"""Pack all entries into the image"""
- self._section.PackEntries()
-
- def CheckSize(self):
- """Check that the image contents does not exceed its size, etc."""
- self._size = self._section.CheckSize()
-
- def CheckEntries(self):
- """Check that entries do not overlap or extend outside the image"""
- self._section.CheckEntries()
-
- def SetCalculatedProperties(self):
- self._section.SetCalculatedProperties()
+ section.Entry_section.Pack(self, 0)
def SetImagePos(self):
- self._section.SetImagePos(0)
+ # This first section in the image so it starts at 0
+ section.Entry_section.SetImagePos(self, 0)
def ProcessEntryContents(self):
"""Call the ProcessContents() method for each entry
This is intended to adjust the contents as needed by the entry type.
+
+ Returns:
+ True if the new data size is OK, False if expansion is needed
"""
- self._section.ProcessEntryContents()
+ sizes_ok = True
+ for entry in self._entries.values():
+ if not entry.ProcessContents():
+ sizes_ok = False
+ tout.Debug("Entry '%s' size change" % self._node.path)
+ return sizes_ok
def WriteSymbols(self):
"""Write symbol values into binary files for access at run time"""
- self._section.WriteSymbols()
+ section.Entry_section.WriteSymbols(self, self)
+
+ def BuildSection(self, fd, base_offset):
+ """Write the section to a file"""
+ fd.seek(base_offset)
+ fd.write(self.GetData())
def BuildImage(self):
"""Write the image to a file"""
fname = tools.GetOutputFilename(self._filename)
with open(fname, 'wb') as fd:
- self._section.BuildSection(fd, 0)
-
- def GetEntries(self):
- return self._section.GetEntries()
+ self.BuildSection(fd, 0)
def WriteMap(self):
"""Write a map of the image to a .map file
@@ -144,10 +134,169 @@
Returns:
Filename of map file written
"""
- filename = '%s.map' % self._name
+ filename = '%s.map' % self.image_name
fname = tools.GetOutputFilename(filename)
with open(fname, 'w') as fd:
print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
file=fd)
- self._section.WriteMap(fd, 0)
+ section.Entry_section.WriteMap(self, fd, 0)
return fname
+
+ def BuildEntryList(self):
+ """List the files in an image
+
+ Returns:
+ List of entry.EntryInfo objects describing all entries in the image
+ """
+ entries = []
+ self.ListEntries(entries, 0)
+ return entries
+
+ def FindEntryPath(self, entry_path):
+ """Find an entry at a given path in the image
+
+ Args:
+ entry_path: Path to entry (e.g. /ro-section/u-boot')
+
+ Returns:
+ Entry object corresponding to that past
+
+ Raises:
+ ValueError if no entry found
+ """
+ parts = entry_path.split('/')
+ entries = self.GetEntries()
+ parent = '/'
+ for part in parts:
+ entry = entries.get(part)
+ if not entry:
+ raise ValueError("Entry '%s' not found in '%s'" %
+ (part, parent))
+ parent = entry.GetPath()
+ entries = entry.GetEntries()
+ return entry
+
+ def ReadData(self, decomp=True):
+ return self._data
+
+ def GetListEntries(self, entry_paths):
+ """List the entries in an image
+
+ This decodes the supplied image and returns a list of entries from that
+ image, preceded by a header.
+
+ Args:
+ entry_paths: List of paths to match (each can have wildcards). Only
+ entries whose names match one of these paths will be printed
+
+ Returns:
+ String error message if something went wrong, otherwise
+ 3-Tuple:
+ List of EntryInfo objects
+ List of lines, each
+ List of text columns, each a string
+ List of widths of each column
+ """
+ def _EntryToStrings(entry):
+ """Convert an entry to a list of strings, one for each column
+
+ Args:
+ entry: EntryInfo object containing information to output
+
+ Returns:
+ List of strings, one for each field in entry
+ """
+ def _AppendHex(val):
+ """Append a hex value, or an empty string if val is None
+
+ Args:
+ val: Integer value, or None if none
+ """
+ args.append('' if val is None else '>%x' % val)
+
+ args = [' ' * entry.indent + entry.name]
+ _AppendHex(entry.image_pos)
+ _AppendHex(entry.size)
+ args.append(entry.etype)
+ _AppendHex(entry.offset)
+ _AppendHex(entry.uncomp_size)
+ return args
+
+ def _DoLine(lines, line):
+ """Add a line to the output list
+
+ This adds a line (a list of columns) to the output list. It also updates
+ the widths[] array with the maximum width of each column
+
+ Args:
+ lines: List of lines to add to
+ line: List of strings, one for each column
+ """
+ for i, item in enumerate(line):
+ widths[i] = max(widths[i], len(item))
+ lines.append(line)
+
+ def _NameInPaths(fname, entry_paths):
+ """Check if a filename is in a list of wildcarded paths
+
+ Args:
+ fname: Filename to check
+ entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
+ 'section/u-boot'])
+
+ Returns:
+ True if any wildcard matches the filename (using Unix filename
+ pattern matching, not regular expressions)
+ False if not
+ """
+ for path in entry_paths:
+ if fnmatch.fnmatch(fname, path):
+ return True
+ return False
+
+ entries = self.BuildEntryList()
+
+ # This is our list of lines. Each item in the list is a list of strings, one
+ # for each column
+ lines = []
+ HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
+ 'Uncomp-size']
+ num_columns = len(HEADER)
+
+ # This records the width of each column, calculated as the maximum width of
+ # all the strings in that column
+ widths = [0] * num_columns
+ _DoLine(lines, HEADER)
+
+ # We won't print anything unless it has at least this indent. So at the
+ # start we will print nothing, unless a path matches (or there are no
+ # entry paths)
+ MAX_INDENT = 100
+ min_indent = MAX_INDENT
+ path_stack = []
+ path = ''
+ indent = 0
+ selected_entries = []
+ for entry in entries:
+ if entry.indent > indent:
+ path_stack.append(path)
+ elif entry.indent < indent:
+ path_stack.pop()
+ if path_stack:
+ path = path_stack[-1] + '/' + entry.name
+ indent = entry.indent
+
+ # If there are entry paths to match and we are not looking at a
+ # sub-entry of a previously matched entry, we need to check the path
+ if entry_paths and indent <= min_indent:
+ if _NameInPaths(path[1:], entry_paths):
+ # Print this entry and all sub-entries (=higher indent)
+ min_indent = indent
+ else:
+ # Don't print this entry, nor any following entries until we get
+ # a path match
+ min_indent = MAX_INDENT
+ continue
+ _DoLine(lines, _EntryToStrings(entry))
+ selected_entries.append(entry)
+ return selected_entries, lines, widths
diff --git a/tools/binman/image_test.py b/tools/binman/image_test.py
index 3775e1a..4004f78 100644
--- a/tools/binman/image_test.py
+++ b/tools/binman/image_test.py
@@ -12,28 +12,25 @@
class TestImage(unittest.TestCase):
def testInvalidFormat(self):
image = Image('name', 'node', test=True)
- section = image._section
with self.assertRaises(ValueError) as e:
- section.LookupSymbol('_binman_something_prop_', False, 'msg')
+ image.LookupSymbol('_binman_something_prop_', False, 'msg')
self.assertIn(
"msg: Symbol '_binman_something_prop_' has invalid format",
str(e.exception))
def testMissingSymbol(self):
image = Image('name', 'node', test=True)
- section = image._section
- section._entries = {}
+ image._entries = {}
with self.assertRaises(ValueError) as e:
- section.LookupSymbol('_binman_type_prop_pname', False, 'msg')
+ image.LookupSymbol('_binman_type_prop_pname', False, 'msg')
self.assertIn("msg: Entry 'type' not found in list ()",
str(e.exception))
def testMissingSymbolOptional(self):
image = Image('name', 'node', test=True)
- section = image._section
- section._entries = {}
+ image._entries = {}
with capture_sys_output() as (stdout, stderr):
- val = section.LookupSymbol('_binman_type_prop_pname', True, 'msg')
+ val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg')
self.assertEqual(val, None)
self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n",
stderr.getvalue())
@@ -41,8 +38,7 @@
def testBadProperty(self):
image = Image('name', 'node', test=True)
- section = image._section
- section._entries = {'u-boot': 1}
+ image._entries = {'u-boot': 1}
with self.assertRaises(ValueError) as e:
- section.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
+ image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
self.assertIn("msg: No such property 'bad", str(e.exception))
diff --git a/tools/binman/state.py b/tools/binman/state.py
index af96786..382bda3 100644
--- a/tools/binman/state.py
+++ b/tools/binman/state.py
@@ -31,6 +31,11 @@
# The DTB which contains the full image information
main_dtb = None
+# Allow entries to expand after they have been packed. This is detected and
+# forces a re-pack. If not allowed, any attempted expansion causes an error in
+# Entry.ProcessContentsUpdate()
+allow_entry_expansion = True
+
def GetFdt(fname):
"""Get the Fdt object for a particular device-tree filename
@@ -59,7 +64,7 @@
"""
return fdt_files[fname]._fname
-def GetFdtContents(fname):
+def GetFdtContents(fname='u-boot.dtb'):
"""Looks up the FDT pathname and contents
This is used to obtain the Fdt pathname and contents when needed by an
@@ -250,3 +255,22 @@
data = m.digest()
for n in GetUpdateNodes(hash_node):
n.SetData('value', data)
+
+def SetAllowEntryExpansion(allow):
+ """Set whether post-pack expansion of entries is allowed
+
+ Args:
+ allow: True to allow expansion, False to raise an exception
+ """
+ global allow_entry_expansion
+
+ allow_entry_expansion = allow
+
+def AllowEntryExpansion():
+ """Check whether post-pack expansion of entries is allowed
+
+ Returns:
+ True if expansion should be allowed, False if an exception should be
+ raised
+ """
+ return allow_entry_expansion
diff --git a/tools/binman/test/066_text.dts b/tools/binman/test/066_text.dts
index 59b1fed..f23a75a 100644
--- a/tools/binman/test/066_text.dts
+++ b/tools/binman/test/066_text.dts
@@ -24,5 +24,10 @@
text-label = "test-id4";
test-id4 = "some text";
};
+ /* Put text directly in the node */
+ text5 {
+ type = "text";
+ text = "more text";
+ };
};
};
diff --git a/tools/binman/test/096_elf.dts b/tools/binman/test/096_elf.dts
index df3440c..8e3f3f1 100644
--- a/tools/binman/test/096_elf.dts
+++ b/tools/binman/test/096_elf.dts
@@ -10,5 +10,7 @@
};
u-boot-spl-elf {
};
+ u-boot-tpl-elf {
+ };
};
};
diff --git a/tools/binman/test/102_cbfs_raw.dts b/tools/binman/test/102_cbfs_raw.dts
new file mode 100644
index 0000000..779cbc1
--- /dev/null
+++ b/tools/binman/test/102_cbfs_raw.dts
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0xb0>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/103_cbfs_raw_ppc.dts b/tools/binman/test/103_cbfs_raw_ppc.dts
new file mode 100644
index 0000000..df1caf0
--- /dev/null
+++ b/tools/binman/test/103_cbfs_raw_ppc.dts
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ cbfs-arch = "ppc64";
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/104_cbfs_stage.dts b/tools/binman/test/104_cbfs_stage.dts
new file mode 100644
index 0000000..215e2f2
--- /dev/null
+++ b/tools/binman/test/104_cbfs_stage.dts
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0xb0>;
+ u-boot {
+ type = "blob";
+ filename = "cbfs-stage.elf";
+ cbfs-type = "stage";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/105_cbfs_raw_compress.dts b/tools/binman/test/105_cbfs_raw_compress.dts
new file mode 100644
index 0000000..646168d
--- /dev/null
+++ b/tools/binman/test/105_cbfs_raw_compress.dts
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x140>;
+ u-boot {
+ type = "text";
+ text = "compress xxxxxxxxxxxxxxxxxxxxxx data";
+ cbfs-type = "raw";
+ cbfs-compress = "lz4";
+ };
+ u-boot-dtb {
+ type = "text";
+ text = "compress xxxxxxxxxxxxxxxxxxxxxx data";
+ cbfs-type = "raw";
+ cbfs-compress = "lzma";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/106_cbfs_bad_arch.dts b/tools/binman/test/106_cbfs_bad_arch.dts
new file mode 100644
index 0000000..4318d45
--- /dev/null
+++ b/tools/binman/test/106_cbfs_bad_arch.dts
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ cbfs-arch = "bad-arch";
+ };
+ };
+};
diff --git a/tools/binman/test/107_cbfs_no_size.dts b/tools/binman/test/107_cbfs_no_size.dts
new file mode 100644
index 0000000..3592f62
--- /dev/null
+++ b/tools/binman/test/107_cbfs_no_size.dts
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ };
+ };
+};
diff --git a/tools/binman/test/108_cbfs_no_contents.dts b/tools/binman/test/108_cbfs_no_contents.dts
new file mode 100644
index 0000000..6233467
--- /dev/null
+++ b/tools/binman/test/108_cbfs_no_contents.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ _testing {
+ return-unknown-contents;
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/109_cbfs_bad_compress.dts b/tools/binman/test/109_cbfs_bad_compress.dts
new file mode 100644
index 0000000..9695024
--- /dev/null
+++ b/tools/binman/test/109_cbfs_bad_compress.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0xb0>;
+ u-boot {
+ cbfs-type = "raw";
+ cbfs-compress = "invalid-algo";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/110_cbfs_name.dts b/tools/binman/test/110_cbfs_name.dts
new file mode 100644
index 0000000..98c16f3
--- /dev/null
+++ b/tools/binman/test/110_cbfs_name.dts
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ u-boot {
+ cbfs-name = "FRED";
+ cbfs-type = "raw";
+ };
+
+ hello {
+ type = "blob";
+ filename = "u-boot.dtb";
+ cbfs-type = "raw";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/111_x86-rom-ifwi.dts b/tools/binman/test/111_x86-rom-ifwi.dts
new file mode 100644
index 0000000..63b5972
--- /dev/null
+++ b/tools/binman/test/111_x86-rom-ifwi.dts
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ end-at-4gb;
+ size = <0x800000>;
+ intel-descriptor {
+ filename = "descriptor.bin";
+ };
+
+ intel-ifwi {
+ offset-unset;
+ filename = "fitimage.bin";
+ convert-fit;
+
+ u-boot-tpl {
+ replace;
+ ifwi-subpart = "IBBP";
+ ifwi-entry = "IBBL";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/112_x86-rom-ifwi-nodesc.dts b/tools/binman/test/112_x86-rom-ifwi-nodesc.dts
new file mode 100644
index 0000000..21ec465
--- /dev/null
+++ b/tools/binman/test/112_x86-rom-ifwi-nodesc.dts
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ end-at-4gb;
+ size = <0x800000>;
+ intel-descriptor {
+ filename = "descriptor.bin";
+ };
+
+ intel-ifwi {
+ offset-unset;
+ filename = "ifwi.bin";
+
+ u-boot-tpl {
+ replace;
+ ifwi-subpart = "IBBP";
+ ifwi-entry = "IBBL";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/113_x86-rom-ifwi-nodata.dts b/tools/binman/test/113_x86-rom-ifwi-nodata.dts
new file mode 100644
index 0000000..62486fd
--- /dev/null
+++ b/tools/binman/test/113_x86-rom-ifwi-nodata.dts
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ end-at-4gb;
+ size = <0x800000>;
+ intel-descriptor {
+ filename = "descriptor.bin";
+ };
+
+ intel-ifwi {
+ offset-unset;
+ filename = "ifwi.bin";
+
+ _testing {
+ return-unknown-contents;
+ replace;
+ ifwi-subpart = "IBBP";
+ ifwi-entry = "IBBL";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/114_cbfs_offset.dts b/tools/binman/test/114_cbfs_offset.dts
new file mode 100644
index 0000000..7aa9d9d
--- /dev/null
+++ b/tools/binman/test/114_cbfs_offset.dts
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ end-at-4gb;
+ size = <0x200>;
+ cbfs {
+ size = <0x200>;
+ offset = <0xfffffe00>;
+ u-boot {
+ cbfs-offset = <0x40>;
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-offset = <0x140>;
+ cbfs-type = "raw";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/115_fdtmap.dts b/tools/binman/test/115_fdtmap.dts
new file mode 100644
index 0000000..2450c41
--- /dev/null
+++ b/tools/binman/test/115_fdtmap.dts
@@ -0,0 +1,13 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ fdtmap {
+ };
+ };
+};
diff --git a/tools/binman/test/116_fdtmap_hdr.dts b/tools/binman/test/116_fdtmap_hdr.dts
new file mode 100644
index 0000000..77a2194
--- /dev/null
+++ b/tools/binman/test/116_fdtmap_hdr.dts
@@ -0,0 +1,17 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0x400>;
+ u-boot {
+ };
+ fdtmap {
+ };
+ image-header {
+ location = "end";
+ };
+ };
+};
diff --git a/tools/binman/test/117_fdtmap_hdr_start.dts b/tools/binman/test/117_fdtmap_hdr_start.dts
new file mode 100644
index 0000000..17b6be0
--- /dev/null
+++ b/tools/binman/test/117_fdtmap_hdr_start.dts
@@ -0,0 +1,19 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0x400>;
+ sort-by-offset;
+ u-boot {
+ offset = <0x100>;
+ };
+ fdtmap {
+ };
+ image-header {
+ location = "start";
+ };
+ };
+};
diff --git a/tools/binman/test/118_fdtmap_hdr_pos.dts b/tools/binman/test/118_fdtmap_hdr_pos.dts
new file mode 100644
index 0000000..fd803f5
--- /dev/null
+++ b/tools/binman/test/118_fdtmap_hdr_pos.dts
@@ -0,0 +1,19 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0x400>;
+ sort-by-offset;
+ u-boot {
+ offset = <0x100>;
+ };
+ fdtmap {
+ };
+ image-header {
+ offset = <0x80>;
+ };
+ };
+};
diff --git a/tools/binman/test/119_fdtmap_hdr_missing.dts b/tools/binman/test/119_fdtmap_hdr_missing.dts
new file mode 100644
index 0000000..41bb680
--- /dev/null
+++ b/tools/binman/test/119_fdtmap_hdr_missing.dts
@@ -0,0 +1,16 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ u-boot {
+ };
+ image-header {
+ offset = <0x80>;
+ location = "start";
+ };
+ };
+};
diff --git a/tools/binman/test/120_hdr_no_location.dts b/tools/binman/test/120_hdr_no_location.dts
new file mode 100644
index 0000000..585e21f
--- /dev/null
+++ b/tools/binman/test/120_hdr_no_location.dts
@@ -0,0 +1,16 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ u-boot {
+ };
+ fdtmap {
+ };
+ image-header {
+ };
+ };
+};
diff --git a/tools/binman/test/121_entry_expand.dts b/tools/binman/test/121_entry_expand.dts
new file mode 100644
index 0000000..ebb7816
--- /dev/null
+++ b/tools/binman/test/121_entry_expand.dts
@@ -0,0 +1,20 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ _testing {
+ bad-update-contents;
+ };
+
+ u-boot {
+ };
+
+ _testing2 {
+ type = "_testing";
+ bad-update-contents;
+ };
+ };
+};
diff --git a/tools/binman/test/122_entry_expand_twice.dts b/tools/binman/test/122_entry_expand_twice.dts
new file mode 100644
index 0000000..258cf85
--- /dev/null
+++ b/tools/binman/test/122_entry_expand_twice.dts
@@ -0,0 +1,21 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ _testing {
+ bad-update-contents;
+ bad-update-contents-twice;
+ };
+
+ u-boot {
+ };
+
+ _testing2 {
+ type = "_testing";
+ bad-update-contents;
+ };
+ };
+};
diff --git a/tools/binman/test/123_entry_expand_section.dts b/tools/binman/test/123_entry_expand_section.dts
new file mode 100644
index 0000000..046f723
--- /dev/null
+++ b/tools/binman/test/123_entry_expand_section.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ _testing {
+ bad-update-contents;
+ };
+
+ u-boot {
+ };
+
+ section {
+ _testing2 {
+ type = "_testing";
+ bad-update-contents;
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/124_compress_dtb.dts b/tools/binman/test/124_compress_dtb.dts
new file mode 100644
index 0000000..46bfd8b
--- /dev/null
+++ b/tools/binman/test/124_compress_dtb.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ u-boot-dtb {
+ compress = "lz4";
+ };
+ };
+};
diff --git a/tools/binman/test/125_cbfs_update.dts b/tools/binman/test/125_cbfs_update.dts
new file mode 100644
index 0000000..6d2e8a0
--- /dev/null
+++ b/tools/binman/test/125_cbfs_update.dts
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ u-boot {
+ cbfs-type = "raw";
+ cbfs-compress = "lz4";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/126_cbfs_bad_type.dts b/tools/binman/test/126_cbfs_bad_type.dts
new file mode 100644
index 0000000..2cd6fc6
--- /dev/null
+++ b/tools/binman/test/126_cbfs_bad_type.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ cbfs {
+ size = <0x100>;
+ u-boot {
+ cbfs-type = "badtype";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/127_list.dts b/tools/binman/test/127_list.dts
new file mode 100644
index 0000000..c1d6fce
--- /dev/null
+++ b/tools/binman/test/127_list.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ section {
+ align = <0x100>;
+ cbfs {
+ size = <0x400>;
+ u-boot {
+ cbfs-type = "raw";
+ cbfs-offset = <0x38>;
+ };
+ u-boot-dtb {
+ type = "text";
+ text = "compress xxxxxxxxxxxxxxxxxxxxxx data";
+ cbfs-type = "raw";
+ cbfs-compress = "lzma";
+ cbfs-offset = <0x78>;
+ };
+ };
+ u-boot-dtb {
+ compress = "lz4";
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/128_decode_image.dts b/tools/binman/test/128_decode_image.dts
new file mode 100644
index 0000000..449fccc
--- /dev/null
+++ b/tools/binman/test/128_decode_image.dts
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0xc00>;
+ u-boot {
+ };
+ section {
+ align = <0x100>;
+ cbfs {
+ size = <0x400>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ cbfs-compress = "lzma";
+ cbfs-offset = <0x80>;
+ };
+ };
+ u-boot-dtb {
+ compress = "lz4";
+ };
+ };
+ fdtmap {
+ };
+ image-header {
+ location = "end";
+ };
+ };
+};
diff --git a/tools/binman/test/129_decode_image_nohdr.dts b/tools/binman/test/129_decode_image_nohdr.dts
new file mode 100644
index 0000000..90fdd88
--- /dev/null
+++ b/tools/binman/test/129_decode_image_nohdr.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0xc00>;
+ u-boot {
+ };
+ section {
+ align = <0x100>;
+ cbfs {
+ size = <0x400>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ cbfs-compress = "lzma";
+ cbfs-offset = <0x80>;
+ };
+ };
+ u-boot-dtb {
+ compress = "lz4";
+ };
+ };
+ fdtmap {
+ };
+ };
+};
diff --git a/tools/binman/test/130_list_fdtmap.dts b/tools/binman/test/130_list_fdtmap.dts
new file mode 100644
index 0000000..449fccc
--- /dev/null
+++ b/tools/binman/test/130_list_fdtmap.dts
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0xc00>;
+ u-boot {
+ };
+ section {
+ align = <0x100>;
+ cbfs {
+ size = <0x400>;
+ u-boot {
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-type = "raw";
+ cbfs-compress = "lzma";
+ cbfs-offset = <0x80>;
+ };
+ };
+ u-boot-dtb {
+ compress = "lz4";
+ };
+ };
+ fdtmap {
+ };
+ image-header {
+ location = "end";
+ };
+ };
+};
diff --git a/tools/binman/test/131_pack_align_section.dts b/tools/binman/test/131_pack_align_section.dts
new file mode 100644
index 0000000..4447885
--- /dev/null
+++ b/tools/binman/test/131_pack_align_section.dts
@@ -0,0 +1,28 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ section0 {
+ type = "section";
+ align = <0x10>;
+ u-boot {
+ };
+ };
+ section1 {
+ type = "section";
+ align-size = <0x20>;
+ u-boot {
+ };
+ section2 {
+ type = "section";
+ u-boot {
+ };
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/fitimage.bin.gz b/tools/binman/test/fitimage.bin.gz
new file mode 100644
index 0000000..0a9dcfc
--- /dev/null
+++ b/tools/binman/test/fitimage.bin.gz
Binary files differ
diff --git a/tools/binman/test/ifwi.bin.gz b/tools/binman/test/ifwi.bin.gz
new file mode 100644
index 0000000..25d7289
--- /dev/null
+++ b/tools/binman/test/ifwi.bin.gz
Binary files differ
diff --git a/tools/buildman/README b/tools/buildman/README
index 56a99c7..e366192 100644
--- a/tools/buildman/README
+++ b/tools/buildman/README
@@ -137,7 +137,7 @@
You can also use -x to specifically exclude some boards. For example:
- buildmand arm -x nvidia,freescale,.*ball$
+ buildman arm -x nvidia,freescale,.*ball$
means to build all arm boards except nvidia, freescale and anything ending
with 'ball'.
@@ -146,7 +146,7 @@
comma-separated list of board target names and be used multiple times on
the command line:
- buidman --boards sandbox,snow --boards
+ buildman --boards sandbox,snow --boards
It is convenient to use the -n option to see what will be built based on
the subset given. Use -v as well to get an actual list of boards.
diff --git a/tools/ifwitool.c b/tools/ifwitool.c
new file mode 100644
index 0000000..2e020a8
--- /dev/null
+++ b/tools/ifwitool.c
@@ -0,0 +1,2304 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ifwitool, CLI utility for Integrated Firmware Image (IFWI) manipulation
+ *
+ * This is taken from the Coreboot project
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <getopt.h>
+#include "os_support.h"
+
+#define __packed __attribute__((packed))
+#define KiB 1024
+#define ALIGN(x, a) __ALIGN_MASK((x), (typeof(x))(a) - 1)
+#define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/*
+ * min()/max()/clamp() macros that also do
+ * strict type-checking.. See the
+ * "unnecessary" pointer comparison.
+ */
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void)&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+
+#define max(x, y) ({ \
+ typeof(x) _max1 = (x); \
+ typeof(y) _max2 = (y); \
+ (void)(&_max1 == &_max2); \
+ _max1 > _max2 ? _max1 : _max2; })
+
+static int verbose = 1;
+
+/* Buffer and file I/O */
+struct buffer {
+ char *name;
+ char *data;
+ size_t offset;
+ size_t size;
+};
+
+#define ERROR(...) { fprintf(stderr, "E: " __VA_ARGS__); }
+#define INFO(...) { if (verbose > 0) fprintf(stderr, "INFO: " __VA_ARGS__); }
+#define DEBUG(...) { if (verbose > 1) fprintf(stderr, "DEBUG: " __VA_ARGS__); }
+
+/*
+ * BPDT is Boot Partition Descriptor Table. It is located at the start of a
+ * logical boot partition(LBP). It stores information about the critical
+ * sub-partitions present within the LBP.
+ *
+ * S-BPDT is Secondary Boot Partition Descriptor Table. It is located after the
+ * critical sub-partitions and contains information about the non-critical
+ * sub-partitions present within the LBP.
+ *
+ * Both tables are identified by BPDT_SIGNATURE stored at the start of the
+ * table.
+ */
+#define BPDT_SIGNATURE (0x000055AA)
+
+/* Parameters passed in by caller */
+static struct param {
+ const char *file_name;
+ const char *subpart_name;
+ const char *image_name;
+ bool dir_ops;
+ const char *dentry_name;
+} param;
+
+struct bpdt_header {
+ /*
+ * This is used to identify start of BPDT. It should always be
+ * BPDT_SIGNATURE.
+ */
+ uint32_t signature;
+ /* Count of BPDT entries present */
+ uint16_t descriptor_count;
+ /* Version - Currently supported = 1 */
+ uint16_t bpdt_version;
+ /* Unused - Should be 0 */
+ uint32_t xor_redundant_block;
+ /* Version of IFWI build */
+ uint32_t ifwi_version;
+ /* Version of FIT tool used to create IFWI */
+ uint64_t fit_tool_version;
+} __packed;
+#define BPDT_HEADER_SIZE (sizeof(struct bpdt_header))
+
+struct bpdt_entry {
+ /* Type of sub-partition */
+ uint16_t type;
+ /* Attributes of sub-partition */
+ uint16_t flags;
+ /* Offset of sub-partition from beginning of LBP */
+ uint32_t offset;
+ /* Size in bytes of sub-partition */
+ uint32_t size;
+} __packed;
+#define BPDT_ENTRY_SIZE (sizeof(struct bpdt_entry))
+
+struct bpdt {
+ struct bpdt_header h;
+ /* In practice, this could be an array of 0 to n entries */
+ struct bpdt_entry e[0];
+} __packed;
+
+static inline size_t get_bpdt_size(struct bpdt_header *h)
+{
+ return (sizeof(*h) + BPDT_ENTRY_SIZE * h->descriptor_count);
+}
+
+/* Minimum size in bytes allocated to BPDT in IFWI */
+#define BPDT_MIN_SIZE ((size_t)512)
+
+/* Header to define directory header for sub-partition */
+struct subpart_dir_header {
+ /* Should be SUBPART_DIR_MARKER */
+ uint32_t marker;
+ /* Number of directory entries in the sub-partition */
+ uint32_t num_entries;
+ /* Currenty supported - 1 */
+ uint8_t header_version;
+ /* Currenty supported - 1 */
+ uint8_t entry_version;
+ /* Length of directory header in bytes */
+ uint8_t header_length;
+ /*
+ * 2s complement of 8-bit sum from first byte of header to last byte of
+ * last directory entry.
+ */
+ uint8_t checksum;
+ /* ASCII short name of sub-partition */
+ uint8_t name[4];
+} __packed;
+#define SUBPART_DIR_HEADER_SIZE \
+ (sizeof(struct subpart_dir_header))
+#define SUBPART_DIR_MARKER 0x44504324
+#define SUBPART_DIR_HEADER_VERSION_SUPPORTED 1
+#define SUBPART_DIR_ENTRY_VERSION_SUPPORTED 1
+
+/* Structure for each directory entry for sub-partition */
+struct subpart_dir_entry {
+ /* Name of directory entry - Not guaranteed to be NULL-terminated */
+ uint8_t name[12];
+ /* Offset of entry from beginning of sub-partition */
+ uint32_t offset;
+ /* Length in bytes of sub-directory entry */
+ uint32_t length;
+ /* Must be zero */
+ uint32_t rsvd;
+} __packed;
+#define SUBPART_DIR_ENTRY_SIZE \
+ (sizeof(struct subpart_dir_entry))
+
+struct subpart_dir {
+ struct subpart_dir_header h;
+ /* In practice, this could be an array of 0 to n entries */
+ struct subpart_dir_entry e[0];
+} __packed;
+
+static inline size_t subpart_dir_size(struct subpart_dir_header *h)
+{
+ return (sizeof(*h) + SUBPART_DIR_ENTRY_SIZE * h->num_entries);
+}
+
+struct manifest_header {
+ uint32_t header_type;
+ uint32_t header_length;
+ uint32_t header_version;
+ uint32_t flags;
+ uint32_t vendor;
+ uint32_t date;
+ uint32_t size;
+ uint32_t id;
+ uint32_t rsvd;
+ uint64_t version;
+ uint32_t svn;
+ uint64_t rsvd1;
+ uint8_t rsvd2[64];
+ uint32_t modulus_size;
+ uint32_t exponent_size;
+ uint8_t public_key[256];
+ uint32_t exponent;
+ uint8_t signature[256];
+} __packed;
+
+#define DWORD_SIZE 4
+#define MANIFEST_HDR_SIZE (sizeof(struct manifest_header))
+#define MANIFEST_ID_MAGIC (0x324e4d24)
+
+struct module {
+ uint8_t name[12];
+ uint8_t type;
+ uint8_t hash_alg;
+ uint16_t hash_size;
+ uint32_t metadata_size;
+ uint8_t metadata_hash[32];
+} __packed;
+
+#define MODULE_SIZE (sizeof(struct module))
+
+struct signed_pkg_info_ext {
+ uint32_t ext_type;
+ uint32_t ext_length;
+ uint8_t name[4];
+ uint32_t vcn;
+ uint8_t bitmap[16];
+ uint32_t svn;
+ uint8_t rsvd[16];
+} __packed;
+
+#define SIGNED_PKG_INFO_EXT_TYPE 0x15
+#define SIGNED_PKG_INFO_EXT_SIZE \
+ (sizeof(struct signed_pkg_info_ext))
+
+/*
+ * Attributes for various IFWI sub-partitions.
+ * LIES_WITHIN_BPDT_4K = Sub-Partition should lie within the same 4K block as
+ * BPDT.
+ * NON_CRITICAL_SUBPART = Sub-Partition entry should be present in S-BPDT.
+ * CONTAINS_DIR = Sub-Partition contains directory.
+ * AUTO_GENERATED = Sub-Partition is generated by the tool.
+ * MANDATORY_BPDT_ENTRY = Even if sub-partition is deleted, BPDT should contain
+ * an entry for it with size 0 and offset 0.
+ */
+enum subpart_attributes {
+ LIES_WITHIN_BPDT_4K = (1 << 0),
+ NON_CRITICAL_SUBPART = (1 << 1),
+ CONTAINS_DIR = (1 << 2),
+ AUTO_GENERATED = (1 << 3),
+ MANDATORY_BPDT_ENTRY = (1 << 4),
+};
+
+/* Type value for various IFWI sub-partitions */
+enum bpdt_entry_type {
+ SMIP_TYPE = 0,
+ CSE_RBE_TYPE = 1,
+ CSE_BUP_TYPE = 2,
+ UCODE_TYPE = 3,
+ IBB_TYPE = 4,
+ S_BPDT_TYPE = 5,
+ OBB_TYPE = 6,
+ CSE_MAIN_TYPE = 7,
+ ISH_TYPE = 8,
+ CSE_IDLM_TYPE = 9,
+ IFP_OVERRIDE_TYPE = 10,
+ DEBUG_TOKENS_TYPE = 11,
+ UFS_PHY_TYPE = 12,
+ UFS_GPP_TYPE = 13,
+ PMC_TYPE = 14,
+ IUNIT_TYPE = 15,
+ NVM_CONFIG_TYPE = 16,
+ UEP_TYPE = 17,
+ UFS_RATE_B_TYPE = 18,
+ MAX_SUBPARTS = 19,
+};
+
+/*
+ * There are two order requirements for an IFWI image:
+ * 1. Order in which the sub-partitions lie within the BPDT entries.
+ * 2. Order in which the sub-partitions lie within the image.
+ *
+ * header_order defines #1 i.e. the order in which the sub-partitions should
+ * appear in the BPDT entries. pack_order defines #2 i.e. the order in which
+ * sub-partitions appear in the IFWI image. pack_order controls the offset and
+ * thus sub-partitions would have increasing offsets as we loop over pack_order.
+ */
+const enum bpdt_entry_type bpdt_header_order[MAX_SUBPARTS] = {
+ /* Order of the following entries is mandatory */
+ CSE_IDLM_TYPE,
+ IFP_OVERRIDE_TYPE,
+ S_BPDT_TYPE,
+ CSE_RBE_TYPE,
+ UFS_PHY_TYPE,
+ UFS_GPP_TYPE,
+ /* Order of the following entries is recommended */
+ UEP_TYPE,
+ NVM_CONFIG_TYPE,
+ UFS_RATE_B_TYPE,
+ IBB_TYPE,
+ SMIP_TYPE,
+ PMC_TYPE,
+ CSE_BUP_TYPE,
+ UCODE_TYPE,
+ DEBUG_TOKENS_TYPE,
+ IUNIT_TYPE,
+ CSE_MAIN_TYPE,
+ ISH_TYPE,
+ OBB_TYPE,
+};
+
+const enum bpdt_entry_type bpdt_pack_order[MAX_SUBPARTS] = {
+ /* Order of the following entries is mandatory */
+ UFS_GPP_TYPE,
+ UFS_PHY_TYPE,
+ IFP_OVERRIDE_TYPE,
+ UEP_TYPE,
+ NVM_CONFIG_TYPE,
+ UFS_RATE_B_TYPE,
+ /* Order of the following entries is recommended */
+ IBB_TYPE,
+ SMIP_TYPE,
+ CSE_RBE_TYPE,
+ PMC_TYPE,
+ CSE_BUP_TYPE,
+ UCODE_TYPE,
+ CSE_IDLM_TYPE,
+ DEBUG_TOKENS_TYPE,
+ S_BPDT_TYPE,
+ IUNIT_TYPE,
+ CSE_MAIN_TYPE,
+ ISH_TYPE,
+ OBB_TYPE,
+};
+
+/* Utility functions */
+enum ifwi_ret {
+ COMMAND_ERR = -1,
+ NO_ACTION_REQUIRED = 0,
+ REPACK_REQUIRED = 1,
+};
+
+struct dir_ops {
+ enum ifwi_ret (*dir_add)(int type);
+};
+
+static enum ifwi_ret ibbp_dir_add(int type);
+
+const struct subpart_info {
+ const char *name;
+ const char *readable_name;
+ uint32_t attr;
+ struct dir_ops dir_ops;
+} subparts[MAX_SUBPARTS] = {
+ /* OEM SMIP */
+ [SMIP_TYPE] = {"SMIP", "SMIP", CONTAINS_DIR, {NULL} },
+ /* CSE RBE */
+ [CSE_RBE_TYPE] = {"RBEP", "CSE_RBE", CONTAINS_DIR |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* CSE BUP */
+ [CSE_BUP_TYPE] = {"FTPR", "CSE_BUP", CONTAINS_DIR |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* uCode */
+ [UCODE_TYPE] = {"UCOD", "Microcode", CONTAINS_DIR, {NULL} },
+ /* IBB */
+ [IBB_TYPE] = {"IBBP", "Bootblock", CONTAINS_DIR, {ibbp_dir_add} },
+ /* S-BPDT */
+ [S_BPDT_TYPE] = {"S_BPDT", "S-BPDT", AUTO_GENERATED |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* OBB */
+ [OBB_TYPE] = {"OBBP", "OEM boot block", CONTAINS_DIR |
+ NON_CRITICAL_SUBPART, {NULL} },
+ /* CSE Main */
+ [CSE_MAIN_TYPE] = {"NFTP", "CSE_MAIN", CONTAINS_DIR |
+ NON_CRITICAL_SUBPART, {NULL} },
+ /* ISH */
+ [ISH_TYPE] = {"ISHP", "ISH", NON_CRITICAL_SUBPART, {NULL} },
+ /* CSE IDLM */
+ [CSE_IDLM_TYPE] = {"DLMP", "CSE_IDLM", CONTAINS_DIR |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* IFP Override */
+ [IFP_OVERRIDE_TYPE] = {"IFP_OVERRIDE", "IFP_OVERRIDE",
+ LIES_WITHIN_BPDT_4K | MANDATORY_BPDT_ENTRY,
+ {NULL} },
+ /* Debug Tokens */
+ [DEBUG_TOKENS_TYPE] = {"DEBUG_TOKENS", "Debug Tokens", 0, {NULL} },
+ /* UFS Phy Configuration */
+ [UFS_PHY_TYPE] = {"UFS_PHY", "UFS Phy", LIES_WITHIN_BPDT_4K |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* UFS GPP LUN ID */
+ [UFS_GPP_TYPE] = {"UFS_GPP", "UFS GPP", LIES_WITHIN_BPDT_4K |
+ MANDATORY_BPDT_ENTRY, {NULL} },
+ /* PMC */
+ [PMC_TYPE] = {"PMCP", "PMC firmware", CONTAINS_DIR, {NULL} },
+ /* IUNIT */
+ [IUNIT_TYPE] = {"IUNP", "IUNIT", NON_CRITICAL_SUBPART, {NULL} },
+ /* NVM Config */
+ [NVM_CONFIG_TYPE] = {"NVM_CONFIG", "NVM Config", 0, {NULL} },
+ /* UEP */
+ [UEP_TYPE] = {"UEP", "UEP", LIES_WITHIN_BPDT_4K | MANDATORY_BPDT_ENTRY,
+ {NULL} },
+ /* UFS Rate B Config */
+ [UFS_RATE_B_TYPE] = {"UFS_RATE_B", "UFS Rate B Config", 0, {NULL} },
+};
+
+struct ifwi_image {
+ /* Data read from input file */
+ struct buffer input_buff;
+
+ /* BPDT header and entries */
+ struct buffer bpdt;
+ size_t input_ifwi_start_offset;
+ size_t input_ifwi_end_offset;
+
+ /* Subpartition content */
+ struct buffer subpart_buf[MAX_SUBPARTS];
+} ifwi_image;
+
+/* Buffer and file I/O */
+static off_t get_file_size(FILE *f)
+{
+ off_t fsize;
+
+ fseek(f, 0, SEEK_END);
+ fsize = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ return fsize;
+}
+
+static inline void *buffer_get(const struct buffer *b)
+{
+ return b->data;
+}
+
+static inline size_t buffer_size(const struct buffer *b)
+{
+ return b->size;
+}
+
+static inline size_t buffer_offset(const struct buffer *b)
+{
+ return b->offset;
+}
+
+/*
+ * Shrink a buffer toward the beginning of its previous space.
+ * Afterward, buffer_delete() remains the means of cleaning it up
+ */
+static inline void buffer_set_size(struct buffer *b, size_t size)
+{
+ b->size = size;
+}
+
+/* Splice a buffer into another buffer. Note that it's up to the caller to
+ * bounds check the offset and size. The resulting buffer is backed by the same
+ * storage as the original, so although it is valid to buffer_delete() either
+ * one of them, doing so releases both simultaneously
+ */
+static void buffer_splice(struct buffer *dest, const struct buffer *src,
+ size_t offset, size_t size)
+{
+ dest->name = src->name;
+ dest->data = src->data + offset;
+ dest->offset = src->offset + offset;
+ dest->size = size;
+}
+
+/*
+ * Shrink a buffer toward the end of its previous space.
+ * Afterward, buffer_delete() remains the means of cleaning it up
+ */
+static inline void buffer_seek(struct buffer *b, size_t size)
+{
+ b->offset += size;
+ b->size -= size;
+ b->data += size;
+}
+
+/* Returns the start of the underlying buffer, with the offset undone */
+static inline void *buffer_get_original_backing(const struct buffer *b)
+{
+ if (!b)
+ return NULL;
+ return buffer_get(b) - buffer_offset(b);
+}
+
+int buffer_create(struct buffer *buffer, size_t size, const char *name)
+{
+ buffer->name = strdup(name);
+ buffer->offset = 0;
+ buffer->size = size;
+ buffer->data = (char *)malloc(buffer->size);
+ if (!buffer->data) {
+ fprintf(stderr, "%s: Insufficient memory (0x%zx).\n", __func__,
+ size);
+ }
+
+ return !buffer->data;
+}
+
+int buffer_write_file(struct buffer *buffer, const char *filename)
+{
+ FILE *fp = fopen(filename, "wb");
+
+ if (!fp) {
+ perror(filename);
+ return -1;
+ }
+ assert(buffer && buffer->data);
+ if (fwrite(buffer->data, 1, buffer->size, fp) != buffer->size) {
+ fprintf(stderr, "incomplete write: %s\n", filename);
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+ return 0;
+}
+
+void buffer_delete(struct buffer *buffer)
+{
+ assert(buffer);
+ if (buffer->name) {
+ free(buffer->name);
+ buffer->name = NULL;
+ }
+ if (buffer->data) {
+ free(buffer_get_original_backing(buffer));
+ buffer->data = NULL;
+ }
+ buffer->offset = 0;
+ buffer->size = 0;
+}
+
+int buffer_from_file(struct buffer *buffer, const char *filename)
+{
+ FILE *fp = fopen(filename, "rb");
+
+ if (!fp) {
+ perror(filename);
+ return -1;
+ }
+ buffer->offset = 0;
+ off_t file_size = get_file_size(fp);
+
+ if (file_size < 0) {
+ fprintf(stderr, "could not determine size of %s\n", filename);
+ fclose(fp);
+ return -1;
+ }
+ buffer->size = file_size;
+ buffer->name = strdup(filename);
+ buffer->data = (char *)malloc(buffer->size);
+ assert(buffer->data);
+ if (fread(buffer->data, 1, buffer->size, fp) != buffer->size) {
+ fprintf(stderr, "incomplete read: %s\n", filename);
+ fclose(fp);
+ buffer_delete(buffer);
+ return -1;
+ }
+ fclose(fp);
+ return 0;
+}
+
+static void alloc_buffer(struct buffer *b, size_t s, const char *n)
+{
+ if (buffer_create(b, s, n) == 0)
+ return;
+
+ ERROR("Buffer allocation failure for %s (size = %zx).\n", n, s);
+ exit(-1);
+}
+
+/* Little-Endian functions */
+static inline uint8_t read_ble8(const void *src)
+{
+ const uint8_t *s = src;
+ return *s;
+}
+
+static inline uint8_t read_at_ble8(const void *src, size_t offset)
+{
+ const uint8_t *s = src;
+
+ s += offset;
+ return read_ble8(s);
+}
+
+static inline void write_ble8(void *dest, uint8_t val)
+{
+ *(uint8_t *)dest = val;
+}
+
+static inline void write_at_ble8(void *dest, uint8_t val, size_t offset)
+{
+ uint8_t *d = dest;
+
+ d += offset;
+ write_ble8(d, val);
+}
+
+static inline uint8_t read_at_le8(const void *src, size_t offset)
+{
+ return read_at_ble8(src, offset);
+}
+
+static inline void write_le8(void *dest, uint8_t val)
+{
+ write_ble8(dest, val);
+}
+
+static inline void write_at_le8(void *dest, uint8_t val, size_t offset)
+{
+ write_at_ble8(dest, val, offset);
+}
+
+static inline uint16_t read_le16(const void *src)
+{
+ const uint8_t *s = src;
+
+ return (((uint16_t)s[1]) << 8) | (((uint16_t)s[0]) << 0);
+}
+
+static inline uint16_t read_at_le16(const void *src, size_t offset)
+{
+ const uint8_t *s = src;
+
+ s += offset;
+ return read_le16(s);
+}
+
+static inline void write_le16(void *dest, uint16_t val)
+{
+ write_le8(dest, val >> 0);
+ write_at_le8(dest, val >> 8, sizeof(uint8_t));
+}
+
+static inline void write_at_le16(void *dest, uint16_t val, size_t offset)
+{
+ uint8_t *d = dest;
+
+ d += offset;
+ write_le16(d, val);
+}
+
+static inline uint32_t read_le32(const void *src)
+{
+ const uint8_t *s = src;
+
+ return (((uint32_t)s[3]) << 24) | (((uint32_t)s[2]) << 16) |
+ (((uint32_t)s[1]) << 8) | (((uint32_t)s[0]) << 0);
+}
+
+static inline uint32_t read_at_le32(const void *src, size_t offset)
+{
+ const uint8_t *s = src;
+
+ s += offset;
+ return read_le32(s);
+}
+
+static inline void write_le32(void *dest, uint32_t val)
+{
+ write_le16(dest, val >> 0);
+ write_at_le16(dest, val >> 16, sizeof(uint16_t));
+}
+
+static inline void write_at_le32(void *dest, uint32_t val, size_t offset)
+{
+ uint8_t *d = dest;
+
+ d += offset;
+ write_le32(d, val);
+}
+
+static inline uint64_t read_le64(const void *src)
+{
+ uint64_t val;
+
+ val = read_at_le32(src, sizeof(uint32_t));
+ val <<= 32;
+ val |= read_le32(src);
+ return val;
+}
+
+static inline uint64_t read_at_le64(const void *src, size_t offset)
+{
+ const uint8_t *s = src;
+
+ s += offset;
+ return read_le64(s);
+}
+
+static inline void write_le64(void *dest, uint64_t val)
+{
+ write_le32(dest, val >> 0);
+ write_at_le32(dest, val >> 32, sizeof(uint32_t));
+}
+
+static inline void write_at_le64(void *dest, uint64_t val, size_t offset)
+{
+ uint8_t *d = dest;
+
+ d += offset;
+ write_le64(d, val);
+}
+
+/*
+ * Read header/entry members in little-endian format.
+ * Returns the offset upto which the read was performed.
+ */
+static size_t read_member(void *src, size_t offset, size_t size_bytes,
+ void *dst)
+{
+ switch (size_bytes) {
+ case 1:
+ *(uint8_t *)dst = read_at_le8(src, offset);
+ break;
+ case 2:
+ *(uint16_t *)dst = read_at_le16(src, offset);
+ break;
+ case 4:
+ *(uint32_t *)dst = read_at_le32(src, offset);
+ break;
+ case 8:
+ *(uint64_t *)dst = read_at_le64(src, offset);
+ break;
+ default:
+ ERROR("Read size not supported %zd\n", size_bytes);
+ exit(-1);
+ }
+
+ return (offset + size_bytes);
+}
+
+/*
+ * Convert to little endian format.
+ * Returns the offset upto which the fixup was performed.
+ */
+static size_t fix_member(void *data, size_t offset, size_t size_bytes)
+{
+ uint8_t *src = (uint8_t *)data + offset;
+
+ switch (size_bytes) {
+ case 1:
+ write_at_le8(data, *(uint8_t *)src, offset);
+ break;
+ case 2:
+ write_at_le16(data, *(uint16_t *)src, offset);
+ break;
+ case 4:
+ write_at_le32(data, *(uint32_t *)src, offset);
+ break;
+ case 8:
+ write_at_le64(data, *(uint64_t *)src, offset);
+ break;
+ default:
+ ERROR("Write size not supported %zd\n", size_bytes);
+ exit(-1);
+ }
+ return (offset + size_bytes);
+}
+
+static void print_subpart_dir(struct subpart_dir *s)
+{
+ if (verbose == 0)
+ return;
+
+ size_t i;
+
+ printf("%-25s 0x%-23.8x\n", "Marker", s->h.marker);
+ printf("%-25s %-25d\n", "Num entries", s->h.num_entries);
+ printf("%-25s %-25d\n", "Header Version", s->h.header_version);
+ printf("%-25s %-25d\n", "Entry Version", s->h.entry_version);
+ printf("%-25s 0x%-23x\n", "Header Length", s->h.header_length);
+ printf("%-25s 0x%-23x\n", "Checksum", s->h.checksum);
+ printf("%-25s ", "Name");
+ for (i = 0; i < sizeof(s->h.name); i++)
+ printf("%c", s->h.name[i]);
+
+ printf("\n");
+
+ printf("%-25s%-25s%-25s%-25s%-25s\n", "Entry #", "Name", "Offset",
+ "Length", "Rsvd");
+
+ printf("=========================================================================================================================\n");
+
+ for (i = 0; i < s->h.num_entries; i++) {
+ printf("%-25zd%-25.12s0x%-23x0x%-23x0x%-23x\n", i + 1,
+ s->e[i].name, s->e[i].offset, s->e[i].length,
+ s->e[i].rsvd);
+ }
+
+ printf("=========================================================================================================================\n");
+}
+
+static void bpdt_print_header(struct bpdt_header *h, const char *name)
+{
+ if (verbose == 0)
+ return;
+
+ printf("%-25s %-25s\n", "Header", name);
+ printf("%-25s 0x%-23.8x\n", "Signature", h->signature);
+ printf("%-25s %-25d\n", "Descriptor count", h->descriptor_count);
+ printf("%-25s %-25d\n", "BPDT Version", h->bpdt_version);
+ printf("%-25s 0x%-23x\n", "XOR checksum", h->xor_redundant_block);
+ printf("%-25s 0x%-23x\n", "IFWI Version", h->ifwi_version);
+ printf("%-25s 0x%-23llx\n", "FIT Tool Version",
+ (long long)h->fit_tool_version);
+}
+
+static void bpdt_print_entries(struct bpdt_entry *e, size_t count,
+ const char *name)
+{
+ size_t i;
+
+ if (verbose == 0)
+ return;
+
+ printf("%s entries\n", name);
+
+ printf("%-25s%-25s%-25s%-25s%-25s%-25s%-25s%-25s\n", "Entry #",
+ "Sub-Partition", "Name", "Type", "Flags", "Offset", "Size",
+ "File Offset");
+
+ printf("=========================================================================================================================================================================================================\n");
+
+ for (i = 0; i < count; i++) {
+ printf("%-25zd%-25s%-25s%-25d0x%-23.08x0x%-23x0x%-23x0x%-23zx\n",
+ i + 1, subparts[e[i].type].name,
+ subparts[e[i].type].readable_name, e[i].type, e[i].flags,
+ e[i].offset, e[i].size,
+ e[i].offset + ifwi_image.input_ifwi_start_offset);
+ }
+
+ printf("=========================================================================================================================================================================================================\n");
+}
+
+static void bpdt_validate_header(struct bpdt_header *h, const char *name)
+{
+ assert(h->signature == BPDT_SIGNATURE);
+
+ if (h->bpdt_version != 1) {
+ ERROR("Invalid header : %s\n", name);
+ exit(-1);
+ }
+
+ DEBUG("Validated header : %s\n", name);
+}
+
+static void bpdt_read_header(void *data, struct bpdt_header *h,
+ const char *name)
+{
+ size_t offset = 0;
+
+ offset = read_member(data, offset, sizeof(h->signature), &h->signature);
+ offset = read_member(data, offset, sizeof(h->descriptor_count),
+ &h->descriptor_count);
+ offset = read_member(data, offset, sizeof(h->bpdt_version),
+ &h->bpdt_version);
+ offset = read_member(data, offset, sizeof(h->xor_redundant_block),
+ &h->xor_redundant_block);
+ offset = read_member(data, offset, sizeof(h->ifwi_version),
+ &h->ifwi_version);
+ read_member(data, offset, sizeof(h->fit_tool_version),
+ &h->fit_tool_version);
+
+ bpdt_validate_header(h, name);
+ bpdt_print_header(h, name);
+}
+
+static void bpdt_read_entries(void *data, struct bpdt *bpdt, const char *name)
+{
+ size_t i, offset = 0;
+ struct bpdt_entry *e = &bpdt->e[0];
+ size_t count = bpdt->h.descriptor_count;
+
+ for (i = 0; i < count; i++) {
+ offset = read_member(data, offset, sizeof(e[i].type),
+ &e[i].type);
+ offset = read_member(data, offset, sizeof(e[i].flags),
+ &e[i].flags);
+ offset = read_member(data, offset, sizeof(e[i].offset),
+ &e[i].offset);
+ offset = read_member(data, offset, sizeof(e[i].size),
+ &e[i].size);
+ }
+
+ bpdt_print_entries(e, count, name);
+}
+
+/*
+ * Given type of sub-partition, identify BPDT entry for it.
+ * Sub-Partition could lie either within BPDT or S-BPDT.
+ */
+static struct bpdt_entry *__find_entry_by_type(struct bpdt_entry *e,
+ size_t count, int type)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ if (e[i].type == type)
+ break;
+ }
+
+ if (i == count)
+ return NULL;
+
+ return &e[i];
+}
+
+static struct bpdt_entry *find_entry_by_type(int type)
+{
+ struct bpdt *b = buffer_get(&ifwi_image.bpdt);
+
+ if (!b)
+ return NULL;
+
+ struct bpdt_entry *curr = __find_entry_by_type(&b->e[0],
+ b->h.descriptor_count,
+ type);
+
+ if (curr)
+ return curr;
+
+ b = buffer_get(&ifwi_image.subpart_buf[S_BPDT_TYPE]);
+ if (!b)
+ return NULL;
+
+ return __find_entry_by_type(&b->e[0], b->h.descriptor_count, type);
+}
+
+/*
+ * Find sub-partition type given its name. If the name does not exist, returns
+ * -1.
+ */
+static int find_type_by_name(const char *name)
+{
+ int i;
+
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ if ((strlen(subparts[i].name) == strlen(name)) &&
+ (!strcmp(subparts[i].name, name)))
+ break;
+ }
+
+ if (i == MAX_SUBPARTS) {
+ ERROR("Invalid sub-partition name %s.\n", name);
+ return -1;
+ }
+
+ return i;
+}
+
+/*
+ * Read the content of a sub-partition from input file and store it in
+ * ifwi_image.subpart_buf[SUB-PARTITION_TYPE].
+ *
+ * Returns the maximum offset occupied by the sub-partitions.
+ */
+static size_t read_subpart_buf(void *data, size_t size, struct bpdt_entry *e,
+ size_t count)
+{
+ size_t i, type;
+ struct buffer *buf;
+ size_t max_offset = 0;
+
+ for (i = 0; i < count; i++) {
+ type = e[i].type;
+
+ if (type >= MAX_SUBPARTS) {
+ ERROR("Invalid sub-partition type %zd.\n", type);
+ exit(-1);
+ }
+
+ if (buffer_size(&ifwi_image.subpart_buf[type])) {
+ ERROR("Multiple sub-partitions of type %zd(%s).\n",
+ type, subparts[type].name);
+ exit(-1);
+ }
+
+ if (e[i].size == 0) {
+ INFO("Dummy sub-partition %zd(%s). Skipping.\n", type,
+ subparts[type].name);
+ continue;
+ }
+
+ assert((e[i].offset + e[i].size) <= size);
+
+ /*
+ * Sub-partitions in IFWI image are not in the same order as
+ * in BPDT entries. BPDT entires are in header_order whereas
+ * sub-partition offsets in the image are in pack_order.
+ */
+ if ((e[i].offset + e[i].size) > max_offset)
+ max_offset = e[i].offset + e[i].size;
+
+ /*
+ * S-BPDT sub-partition contains information about all the
+ * non-critical sub-partitions. Thus, size of S-BPDT
+ * sub-partition equals size of S-BPDT plus size of all the
+ * non-critical sub-partitions. Thus, reading whole of S-BPDT
+ * here would be redundant as the non-critical partitions are
+ * read and allocated buffers separately. Also, S-BPDT requires
+ * special handling for reading header and entries.
+ */
+ if (type == S_BPDT_TYPE)
+ continue;
+
+ buf = &ifwi_image.subpart_buf[type];
+
+ alloc_buffer(buf, e[i].size, subparts[type].name);
+ memcpy(buffer_get(buf), (uint8_t *)data + e[i].offset,
+ e[i].size);
+ }
+
+ assert(max_offset);
+ return max_offset;
+}
+
+/*
+ * Allocate buffer for bpdt header, entries and all sub-partition content.
+ * Returns offset in data where BPDT ends.
+ */
+static size_t alloc_bpdt_buffer(void *data, size_t size, size_t offset,
+ struct buffer *b, const char *name)
+{
+ struct bpdt_header bpdt_header;
+
+ assert((offset + BPDT_HEADER_SIZE) < size);
+ bpdt_read_header((uint8_t *)data + offset, &bpdt_header, name);
+
+ /* Buffer to read BPDT header and entries */
+ alloc_buffer(b, get_bpdt_size(&bpdt_header), name);
+
+ struct bpdt *bpdt = buffer_get(b);
+
+ memcpy(&bpdt->h, &bpdt_header, BPDT_HEADER_SIZE);
+
+ /*
+ * If no entries are present, maximum offset occupied is (offset +
+ * BPDT_HEADER_SIZE).
+ */
+ if (bpdt->h.descriptor_count == 0)
+ return (offset + BPDT_HEADER_SIZE);
+
+ /* Read all entries */
+ assert((offset + get_bpdt_size(&bpdt->h)) < size);
+ bpdt_read_entries((uint8_t *)data + offset + BPDT_HEADER_SIZE, bpdt,
+ name);
+
+ /* Read all sub-partition content in subpart_buf */
+ return read_subpart_buf(data, size, &bpdt->e[0],
+ bpdt->h.descriptor_count);
+}
+
+static void parse_sbpdt(void *data, size_t size)
+{
+ struct bpdt_entry *s;
+
+ s = find_entry_by_type(S_BPDT_TYPE);
+ if (!s)
+ return;
+
+ assert(size > s->offset);
+
+ alloc_bpdt_buffer(data, size, s->offset,
+ &ifwi_image.subpart_buf[S_BPDT_TYPE],
+ "S-BPDT");
+}
+
+static uint8_t calc_checksum(struct subpart_dir *s)
+{
+ size_t size = subpart_dir_size(&s->h);
+ uint8_t *data = (uint8_t *)s;
+ uint8_t checksum = 0;
+ size_t i;
+ uint8_t old_checksum = s->h.checksum;
+
+ s->h.checksum = 0;
+
+ for (i = 0; i < size; i++)
+ checksum += data[i];
+
+ s->h.checksum = old_checksum;
+
+ /* 2s complement */
+ return -checksum;
+}
+
+static void validate_subpart_dir(struct subpart_dir *s, const char *name,
+ bool checksum_check)
+{
+ if (s->h.marker != SUBPART_DIR_MARKER ||
+ s->h.header_version != SUBPART_DIR_HEADER_VERSION_SUPPORTED ||
+ s->h.entry_version != SUBPART_DIR_ENTRY_VERSION_SUPPORTED ||
+ s->h.header_length != SUBPART_DIR_HEADER_SIZE) {
+ ERROR("Invalid subpart_dir for %s.\n", name);
+ exit(-1);
+ }
+
+ if (!checksum_check)
+ return;
+
+ uint8_t checksum = calc_checksum(s);
+
+ if (checksum != s->h.checksum)
+ ERROR("Invalid checksum for %s (Expected=0x%x, Actual=0x%x).\n",
+ name, checksum, s->h.checksum);
+}
+
+static void validate_subpart_dir_without_checksum(struct subpart_dir *s,
+ const char *name)
+{
+ validate_subpart_dir(s, name, 0);
+}
+
+static void validate_subpart_dir_with_checksum(struct subpart_dir *s,
+ const char *name)
+{
+ validate_subpart_dir(s, name, 1);
+}
+
+static void parse_subpart_dir(struct buffer *subpart_dir_buf,
+ struct buffer *input_buf, const char *name)
+{
+ struct subpart_dir_header hdr;
+ size_t offset = 0;
+ uint8_t *data = buffer_get(input_buf);
+ size_t size = buffer_size(input_buf);
+
+ /* Read Subpart_Dir header */
+ assert(size >= SUBPART_DIR_HEADER_SIZE);
+ offset = read_member(data, offset, sizeof(hdr.marker), &hdr.marker);
+ offset = read_member(data, offset, sizeof(hdr.num_entries),
+ &hdr.num_entries);
+ offset = read_member(data, offset, sizeof(hdr.header_version),
+ &hdr.header_version);
+ offset = read_member(data, offset, sizeof(hdr.entry_version),
+ &hdr.entry_version);
+ offset = read_member(data, offset, sizeof(hdr.header_length),
+ &hdr.header_length);
+ offset = read_member(data, offset, sizeof(hdr.checksum), &hdr.checksum);
+ memcpy(hdr.name, data + offset, sizeof(hdr.name));
+ offset += sizeof(hdr.name);
+
+ validate_subpart_dir_without_checksum((struct subpart_dir *)&hdr, name);
+
+ assert(size > subpart_dir_size(&hdr));
+ alloc_buffer(subpart_dir_buf, subpart_dir_size(&hdr), "Subpart Dir");
+ memcpy(buffer_get(subpart_dir_buf), &hdr, SUBPART_DIR_HEADER_SIZE);
+
+ /* Read Subpart Dir entries */
+ struct subpart_dir *subpart_dir = buffer_get(subpart_dir_buf);
+ struct subpart_dir_entry *e = &subpart_dir->e[0];
+ uint32_t i;
+
+ for (i = 0; i < hdr.num_entries; i++) {
+ memcpy(e[i].name, data + offset, sizeof(e[i].name));
+ offset += sizeof(e[i].name);
+ offset = read_member(data, offset, sizeof(e[i].offset),
+ &e[i].offset);
+ offset = read_member(data, offset, sizeof(e[i].length),
+ &e[i].length);
+ offset = read_member(data, offset, sizeof(e[i].rsvd),
+ &e[i].rsvd);
+ }
+
+ validate_subpart_dir_with_checksum(subpart_dir, name);
+
+ print_subpart_dir(subpart_dir);
+}
+
+/* Parse input image file to identify different sub-partitions */
+static int ifwi_parse(void)
+{
+ struct buffer *buff = &ifwi_image.input_buff;
+ const char *image_name = param.image_name;
+
+ DEBUG("Parsing IFWI image...\n");
+
+ /* Read input file */
+ if (buffer_from_file(buff, image_name)) {
+ ERROR("Failed to read input file %s.\n", image_name);
+ return -1;
+ }
+
+ INFO("Buffer %p size 0x%zx\n", buff->data, buff->size);
+
+ /* Look for BPDT signature at 4K intervals */
+ size_t offset = 0;
+ void *data = buffer_get(buff);
+
+ while (offset < buffer_size(buff)) {
+ if (read_at_le32(data, offset) == BPDT_SIGNATURE)
+ break;
+ offset += 4 * KiB;
+ }
+
+ if (offset >= buffer_size(buff)) {
+ ERROR("Image does not contain BPDT!!\n");
+ return -1;
+ }
+
+ ifwi_image.input_ifwi_start_offset = offset;
+ INFO("BPDT starts at offset 0x%zx.\n", offset);
+
+ data = (uint8_t *)data + offset;
+ size_t ifwi_size = buffer_size(buff) - offset;
+
+ /* Read BPDT and sub-partitions */
+ uintptr_t end_offset;
+
+ end_offset = ifwi_image.input_ifwi_start_offset +
+ alloc_bpdt_buffer(data, ifwi_size, 0, &ifwi_image.bpdt, "BPDT");
+
+ /* Parse S-BPDT, if any */
+ parse_sbpdt(data, ifwi_size);
+
+ /*
+ * Store end offset of IFWI. Required for copying any trailing non-IFWI
+ * part of the image.
+ * ASSUMPTION: IFWI image always ends on a 4K boundary.
+ */
+ ifwi_image.input_ifwi_end_offset = ALIGN(end_offset, 4 * KiB);
+ DEBUG("Parsing done.\n");
+
+ return 0;
+}
+
+/*
+ * This function is used by repack to count the number of BPDT and S-BPDT
+ * entries that are present. It frees the current buffers used by the entries
+ * and allocates fresh buffers that can be used for repacking. Returns BPDT
+ * entries which are empty and need to be filled in.
+ */
+static void __bpdt_reset(struct buffer *b, size_t count, size_t size)
+{
+ size_t bpdt_size = BPDT_HEADER_SIZE + count * BPDT_ENTRY_SIZE;
+
+ assert(size >= bpdt_size);
+
+ /*
+ * If buffer does not have the required size, allocate a fresh buffer.
+ */
+ if (buffer_size(b) != size) {
+ struct buffer temp;
+
+ alloc_buffer(&temp, size, b->name);
+ memcpy(buffer_get(&temp), buffer_get(b), buffer_size(b));
+ buffer_delete(b);
+ *b = temp;
+ }
+
+ struct bpdt *bpdt = buffer_get(b);
+ uint8_t *ptr = (uint8_t *)&bpdt->e[0];
+ size_t entries_size = BPDT_ENTRY_SIZE * count;
+
+ /* Zero out BPDT entries */
+ memset(ptr, 0, entries_size);
+ /* Fill any pad-space with FF */
+ memset(ptr + entries_size, 0xFF, size - bpdt_size);
+
+ bpdt->h.descriptor_count = count;
+}
+
+static void bpdt_reset(void)
+{
+ size_t i;
+ size_t bpdt_count = 0, sbpdt_count = 0, dummy_bpdt_count = 0;
+
+ /* Count number of BPDT and S-BPDT entries */
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ if (buffer_size(&ifwi_image.subpart_buf[i]) == 0) {
+ if (subparts[i].attr & MANDATORY_BPDT_ENTRY) {
+ bpdt_count++;
+ dummy_bpdt_count++;
+ }
+ continue;
+ }
+
+ if (subparts[i].attr & NON_CRITICAL_SUBPART)
+ sbpdt_count++;
+ else
+ bpdt_count++;
+ }
+
+ DEBUG("Count: BPDT = %zd, Dummy BPDT = %zd, S-BPDT = %zd\n", bpdt_count,
+ dummy_bpdt_count, sbpdt_count);
+
+ /* Update BPDT if required */
+ size_t bpdt_size = max(BPDT_MIN_SIZE,
+ BPDT_HEADER_SIZE + bpdt_count * BPDT_ENTRY_SIZE);
+ __bpdt_reset(&ifwi_image.bpdt, bpdt_count, bpdt_size);
+
+ /* Update S-BPDT if required */
+ bpdt_size = ALIGN(BPDT_HEADER_SIZE + sbpdt_count * BPDT_ENTRY_SIZE,
+ 4 * KiB);
+ __bpdt_reset(&ifwi_image.subpart_buf[S_BPDT_TYPE], sbpdt_count,
+ bpdt_size);
+}
+
+/* Initialize BPDT entries in header order */
+static void bpdt_entries_init_header_order(void)
+{
+ int i, type;
+ size_t size;
+
+ struct bpdt *bpdt, *sbpdt, *curr;
+ size_t bpdt_curr = 0, sbpdt_curr = 0, *count_ptr;
+
+ bpdt = buffer_get(&ifwi_image.bpdt);
+ sbpdt = buffer_get(&ifwi_image.subpart_buf[S_BPDT_TYPE]);
+
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ type = bpdt_header_order[i];
+ size = buffer_size(&ifwi_image.subpart_buf[type]);
+
+ if (size == 0 && !(subparts[type].attr & MANDATORY_BPDT_ENTRY))
+ continue;
+
+ if (subparts[type].attr & NON_CRITICAL_SUBPART) {
+ curr = sbpdt;
+ count_ptr = &sbpdt_curr;
+ } else {
+ curr = bpdt;
+ count_ptr = &bpdt_curr;
+ }
+
+ assert(*count_ptr < curr->h.descriptor_count);
+ curr->e[*count_ptr].type = type;
+ curr->e[*count_ptr].flags = 0;
+ curr->e[*count_ptr].offset = 0;
+ curr->e[*count_ptr].size = size;
+
+ (*count_ptr)++;
+ }
+}
+
+static void pad_buffer(struct buffer *b, size_t size)
+{
+ size_t buff_size = buffer_size(b);
+
+ assert(buff_size <= size);
+
+ if (buff_size == size)
+ return;
+
+ struct buffer temp;
+
+ alloc_buffer(&temp, size, b->name);
+ uint8_t *data = buffer_get(&temp);
+
+ memcpy(data, buffer_get(b), buff_size);
+ memset(data + buff_size, 0xFF, size - buff_size);
+
+ *b = temp;
+}
+
+/* Initialize offsets of entries using pack order */
+static void bpdt_entries_init_pack_order(void)
+{
+ int i, type;
+ struct bpdt_entry *curr;
+ size_t curr_offset, curr_end;
+
+ curr_offset = max(BPDT_MIN_SIZE, buffer_size(&ifwi_image.bpdt));
+
+ /*
+ * There are two types of sub-partitions that need to be handled here:
+ * 1. Sub-partitions that lie within the same 4K as BPDT
+ * 2. Sub-partitions that lie outside the 4K of BPDT
+ *
+ * For sub-partitions of type # 1, there is no requirement on the start
+ * or end of the sub-partition. They need to be packed in without any
+ * holes left in between. If there is any empty space left after the end
+ * of the last sub-partition in 4K of BPDT, then that space needs to be
+ * padded with FF bytes, but the size of the last sub-partition remains
+ * unchanged.
+ *
+ * For sub-partitions of type # 2, both the start and end should be a
+ * multiple of 4K. If not, then it needs to be padded with FF bytes and
+ * size adjusted such that the sub-partition ends on 4K boundary.
+ */
+
+ /* #1 Sub-partitions that lie within same 4K as BPDT */
+ struct buffer *last_bpdt_buff = &ifwi_image.bpdt;
+
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ type = bpdt_pack_order[i];
+ curr = find_entry_by_type(type);
+
+ if (!curr || curr->size == 0)
+ continue;
+
+ if (!(subparts[type].attr & LIES_WITHIN_BPDT_4K))
+ continue;
+
+ curr->offset = curr_offset;
+ curr_offset = curr->offset + curr->size;
+ last_bpdt_buff = &ifwi_image.subpart_buf[type];
+ DEBUG("type=%d, curr_offset=0x%zx, curr->offset=0x%x, curr->size=0x%x, buff_size=0x%zx\n",
+ type, curr_offset, curr->offset, curr->size,
+ buffer_size(&ifwi_image.subpart_buf[type]));
+ }
+
+ /* Pad ff bytes if there is any empty space left in BPDT 4K */
+ curr_end = ALIGN(curr_offset, 4 * KiB);
+ pad_buffer(last_bpdt_buff,
+ buffer_size(last_bpdt_buff) + (curr_end - curr_offset));
+ curr_offset = curr_end;
+
+ /* #2 Sub-partitions that lie outside of BPDT 4K */
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ type = bpdt_pack_order[i];
+ curr = find_entry_by_type(type);
+
+ if (!curr || curr->size == 0)
+ continue;
+
+ if (subparts[type].attr & LIES_WITHIN_BPDT_4K)
+ continue;
+
+ assert(curr_offset == ALIGN(curr_offset, 4 * KiB));
+ curr->offset = curr_offset;
+ curr_end = ALIGN(curr->offset + curr->size, 4 * KiB);
+ curr->size = curr_end - curr->offset;
+
+ pad_buffer(&ifwi_image.subpart_buf[type], curr->size);
+
+ curr_offset = curr_end;
+ DEBUG("type=%d, curr_offset=0x%zx, curr->offset=0x%x, curr->size=0x%x, buff_size=0x%zx\n",
+ type, curr_offset, curr->offset, curr->size,
+ buffer_size(&ifwi_image.subpart_buf[type]));
+ }
+
+ /*
+ * Update size of S-BPDT to include size of all non-critical
+ * sub-partitions.
+ *
+ * Assumption: S-BPDT always lies at the end of IFWI image.
+ */
+ curr = find_entry_by_type(S_BPDT_TYPE);
+ assert(curr);
+
+ assert(curr_offset == ALIGN(curr_offset, 4 * KiB));
+ curr->size = curr_offset - curr->offset;
+}
+
+/* Convert all members of BPDT to little-endian format */
+static void bpdt_fixup_write_buffer(struct buffer *buf)
+{
+ struct bpdt *s = buffer_get(buf);
+
+ struct bpdt_header *h = &s->h;
+ struct bpdt_entry *e = &s->e[0];
+
+ size_t count = h->descriptor_count;
+
+ size_t offset = 0;
+
+ offset = fix_member(&h->signature, offset, sizeof(h->signature));
+ offset = fix_member(&h->descriptor_count, offset,
+ sizeof(h->descriptor_count));
+ offset = fix_member(&h->bpdt_version, offset, sizeof(h->bpdt_version));
+ offset = fix_member(&h->xor_redundant_block, offset,
+ sizeof(h->xor_redundant_block));
+ offset = fix_member(&h->ifwi_version, offset, sizeof(h->ifwi_version));
+ offset = fix_member(&h->fit_tool_version, offset,
+ sizeof(h->fit_tool_version));
+
+ uint32_t i;
+
+ for (i = 0; i < count; i++) {
+ offset = fix_member(&e[i].type, offset, sizeof(e[i].type));
+ offset = fix_member(&e[i].flags, offset, sizeof(e[i].flags));
+ offset = fix_member(&e[i].offset, offset, sizeof(e[i].offset));
+ offset = fix_member(&e[i].size, offset, sizeof(e[i].size));
+ }
+}
+
+/* Write BPDT to output buffer after fixup */
+static void bpdt_write(struct buffer *dst, size_t offset, struct buffer *src)
+{
+ bpdt_fixup_write_buffer(src);
+ memcpy(buffer_get(dst) + offset, buffer_get(src), buffer_size(src));
+}
+
+/*
+ * Follows these steps to re-create image:
+ * 1. Write any non-IFWI prefix.
+ * 2. Write out BPDT header and entries.
+ * 3. Write sub-partition buffers to respective offsets.
+ * 4. Write any non-IFWI suffix.
+ *
+ * While performing the above steps, make sure that any empty holes are filled
+ * with FF.
+ */
+static void ifwi_write(const char *image_name)
+{
+ struct bpdt_entry *s = find_entry_by_type(S_BPDT_TYPE);
+
+ assert(s);
+
+ size_t ifwi_start, ifwi_end, file_end;
+
+ ifwi_start = ifwi_image.input_ifwi_start_offset;
+ ifwi_end = ifwi_start + ALIGN(s->offset + s->size, 4 * KiB);
+ file_end = ifwi_end + (buffer_size(&ifwi_image.input_buff) -
+ ifwi_image.input_ifwi_end_offset);
+
+ struct buffer b;
+
+ alloc_buffer(&b, file_end, "Final-IFWI");
+
+ uint8_t *input_data = buffer_get(&ifwi_image.input_buff);
+ uint8_t *output_data = buffer_get(&b);
+
+ DEBUG("ifwi_start:0x%zx, ifwi_end:0x%zx, file_end:0x%zx\n", ifwi_start,
+ ifwi_end, file_end);
+
+ /* Copy non-IFWI prefix, if any */
+ memcpy(output_data, input_data, ifwi_start);
+
+ DEBUG("Copied non-IFWI prefix (offset=0x0, size=0x%zx).\n", ifwi_start);
+
+ struct buffer ifwi;
+
+ buffer_splice(&ifwi, &b, ifwi_start, ifwi_end - ifwi_start);
+ uint8_t *ifwi_data = buffer_get(&ifwi);
+
+ /* Copy sub-partitions using pack_order */
+ struct bpdt_entry *curr;
+ struct buffer *subpart_buf;
+ int i, type;
+
+ for (i = 0; i < MAX_SUBPARTS; i++) {
+ type = bpdt_pack_order[i];
+
+ if (type == S_BPDT_TYPE)
+ continue;
+
+ curr = find_entry_by_type(type);
+
+ if (!curr || !curr->size)
+ continue;
+
+ subpart_buf = &ifwi_image.subpart_buf[type];
+
+ DEBUG("curr->offset=0x%x, curr->size=0x%x, type=%d, write_size=0x%zx\n",
+ curr->offset, curr->size, type, buffer_size(subpart_buf));
+
+ assert((curr->offset + buffer_size(subpart_buf)) <=
+ buffer_size(&ifwi));
+
+ memcpy(ifwi_data + curr->offset, buffer_get(subpart_buf),
+ buffer_size(subpart_buf));
+ }
+
+ /* Copy non-IFWI suffix, if any */
+ if (ifwi_end != file_end) {
+ memcpy(output_data + ifwi_end,
+ input_data + ifwi_image.input_ifwi_end_offset,
+ file_end - ifwi_end);
+ DEBUG("Copied non-IFWI suffix (offset=0x%zx,size=0x%zx).\n",
+ ifwi_end, file_end - ifwi_end);
+ }
+
+ /*
+ * Convert BPDT to little-endian format and write it to output buffer.
+ * S-BPDT is written first and then BPDT.
+ */
+ bpdt_write(&ifwi, s->offset, &ifwi_image.subpart_buf[S_BPDT_TYPE]);
+ bpdt_write(&ifwi, 0, &ifwi_image.bpdt);
+
+ if (buffer_write_file(&b, image_name)) {
+ ERROR("File write error\n");
+ exit(-1);
+ }
+
+ buffer_delete(&b);
+ printf("Image written successfully to %s.\n", image_name);
+}
+
+/*
+ * Calculate size and offset of each sub-partition again since it might have
+ * changed because of add/delete operation. Also, re-create BPDT and S-BPDT
+ * entries and write back the new IFWI image to file.
+ */
+static void ifwi_repack(void)
+{
+ bpdt_reset();
+ bpdt_entries_init_header_order();
+ bpdt_entries_init_pack_order();
+
+ struct bpdt *b = buffer_get(&ifwi_image.bpdt);
+
+ bpdt_print_entries(&b->e[0], b->h.descriptor_count, "BPDT");
+
+ b = buffer_get(&ifwi_image.subpart_buf[S_BPDT_TYPE]);
+ bpdt_print_entries(&b->e[0], b->h.descriptor_count, "S-BPDT");
+
+ DEBUG("Repack done.. writing image.\n");
+ ifwi_write(param.image_name);
+}
+
+static void init_subpart_dir_header(struct subpart_dir_header *hdr,
+ size_t count, const char *name)
+{
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->marker = SUBPART_DIR_MARKER;
+ hdr->num_entries = count;
+ hdr->header_version = SUBPART_DIR_HEADER_VERSION_SUPPORTED;
+ hdr->entry_version = SUBPART_DIR_ENTRY_VERSION_SUPPORTED;
+ hdr->header_length = SUBPART_DIR_HEADER_SIZE;
+ memcpy(hdr->name, name, sizeof(hdr->name));
+}
+
+static size_t init_subpart_dir_entry(struct subpart_dir_entry *e,
+ struct buffer *b, size_t offset)
+{
+ memset(e, 0, sizeof(*e));
+
+ assert(strlen(b->name) <= sizeof(e->name));
+ strncpy((char *)e->name, (char *)b->name, sizeof(e->name));
+ e->offset = offset;
+ e->length = buffer_size(b);
+
+ return (offset + buffer_size(b));
+}
+
+static void init_manifest_header(struct manifest_header *hdr, size_t size)
+{
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->header_type = 0x4;
+ assert((MANIFEST_HDR_SIZE % DWORD_SIZE) == 0);
+ hdr->header_length = MANIFEST_HDR_SIZE / DWORD_SIZE;
+ hdr->header_version = 0x10000;
+ hdr->vendor = 0x8086;
+
+ struct tm *local_time;
+ time_t curr_time;
+ char buffer[11];
+
+ curr_time = time(NULL);
+ local_time = localtime(&curr_time);
+ strftime(buffer, sizeof(buffer), "0x%Y%m%d", local_time);
+ hdr->date = strtoul(buffer, NULL, 16);
+
+ assert((size % DWORD_SIZE) == 0);
+ hdr->size = size / DWORD_SIZE;
+ hdr->id = MANIFEST_ID_MAGIC;
+}
+
+static void init_signed_pkg_info_ext(struct signed_pkg_info_ext *ext,
+ size_t count, const char *name)
+{
+ memset(ext, 0, sizeof(*ext));
+
+ ext->ext_type = SIGNED_PKG_INFO_EXT_TYPE;
+ ext->ext_length = SIGNED_PKG_INFO_EXT_SIZE + count * MODULE_SIZE;
+ memcpy(ext->name, name, sizeof(ext->name));
+}
+
+static void subpart_dir_fixup_write_buffer(struct buffer *buf)
+{
+ struct subpart_dir *s = buffer_get(buf);
+ struct subpart_dir_header *h = &s->h;
+ struct subpart_dir_entry *e = &s->e[0];
+
+ size_t count = h->num_entries;
+ size_t offset = 0;
+
+ offset = fix_member(&h->marker, offset, sizeof(h->marker));
+ offset = fix_member(&h->num_entries, offset, sizeof(h->num_entries));
+ offset = fix_member(&h->header_version, offset,
+ sizeof(h->header_version));
+ offset = fix_member(&h->entry_version, offset,
+ sizeof(h->entry_version));
+ offset = fix_member(&h->header_length, offset,
+ sizeof(h->header_length));
+ offset = fix_member(&h->checksum, offset, sizeof(h->checksum));
+ offset += sizeof(h->name);
+
+ uint32_t i;
+
+ for (i = 0; i < count; i++) {
+ offset += sizeof(e[i].name);
+ offset = fix_member(&e[i].offset, offset, sizeof(e[i].offset));
+ offset = fix_member(&e[i].length, offset, sizeof(e[i].length));
+ offset = fix_member(&e[i].rsvd, offset, sizeof(e[i].rsvd));
+ }
+}
+
+static void create_subpart(struct buffer *dst, struct buffer *info[],
+ size_t count, const char *name)
+{
+ struct buffer subpart_dir_buff;
+ size_t size = SUBPART_DIR_HEADER_SIZE + count * SUBPART_DIR_ENTRY_SIZE;
+
+ alloc_buffer(&subpart_dir_buff, size, "subpart-dir");
+
+ struct subpart_dir_header *h = buffer_get(&subpart_dir_buff);
+ struct subpart_dir_entry *e = (struct subpart_dir_entry *)(h + 1);
+
+ init_subpart_dir_header(h, count, name);
+
+ size_t curr_offset = size;
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ curr_offset = init_subpart_dir_entry(&e[i], info[i],
+ curr_offset);
+ }
+
+ alloc_buffer(dst, curr_offset, name);
+ uint8_t *data = buffer_get(dst);
+
+ for (i = 0; i < count; i++) {
+ memcpy(data + e[i].offset, buffer_get(info[i]),
+ buffer_size(info[i]));
+ }
+
+ h->checksum = calc_checksum(buffer_get(&subpart_dir_buff));
+
+ struct subpart_dir *dir = buffer_get(&subpart_dir_buff);
+
+ print_subpart_dir(dir);
+
+ subpart_dir_fixup_write_buffer(&subpart_dir_buff);
+ memcpy(data, dir, buffer_size(&subpart_dir_buff));
+
+ buffer_delete(&subpart_dir_buff);
+}
+
+static enum ifwi_ret ibbp_dir_add(int type)
+{
+ struct buffer manifest;
+ struct signed_pkg_info_ext *ext;
+ struct buffer ibbl;
+ struct buffer ibb;
+
+#define DUMMY_IBB_SIZE (4 * KiB)
+
+ assert(type == IBB_TYPE);
+
+ /*
+ * Entry # 1 - IBBP.man
+ * Contains manifest header and signed pkg info extension.
+ */
+ size_t size = MANIFEST_HDR_SIZE + SIGNED_PKG_INFO_EXT_SIZE;
+
+ alloc_buffer(&manifest, size, "IBBP.man");
+
+ struct manifest_header *man_hdr = buffer_get(&manifest);
+
+ init_manifest_header(man_hdr, size);
+
+ ext = (struct signed_pkg_info_ext *)(man_hdr + 1);
+
+ init_signed_pkg_info_ext(ext, 0, subparts[type].name);
+
+ /* Entry # 2 - IBBL */
+ if (buffer_from_file(&ibbl, param.file_name))
+ return COMMAND_ERR;
+
+ /* Entry # 3 - IBB */
+ alloc_buffer(&ibb, DUMMY_IBB_SIZE, "IBB");
+ memset(buffer_get(&ibb), 0xFF, DUMMY_IBB_SIZE);
+
+ /* Create subpartition */
+ struct buffer *info[] = {
+ &manifest, &ibbl, &ibb,
+ };
+ create_subpart(&ifwi_image.subpart_buf[type], &info[0],
+ ARRAY_SIZE(info), subparts[type].name);
+
+ return REPACK_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_raw_add(int type)
+{
+ if (buffer_from_file(&ifwi_image.subpart_buf[type], param.file_name))
+ return COMMAND_ERR;
+
+ printf("Sub-partition %s(%d) added from file %s.\n", param.subpart_name,
+ type, param.file_name);
+ return REPACK_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_dir_add(int type)
+{
+ if (!(subparts[type].attr & CONTAINS_DIR) ||
+ !subparts[type].dir_ops.dir_add) {
+ ERROR("Sub-Partition %s(%d) does not support dir ops.\n",
+ subparts[type].name, type);
+ return COMMAND_ERR;
+ }
+
+ if (!param.dentry_name) {
+ ERROR("%s: -e option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ enum ifwi_ret ret = subparts[type].dir_ops.dir_add(type);
+
+ if (ret != COMMAND_ERR)
+ printf("Sub-partition %s(%d) entry %s added from file %s.\n",
+ param.subpart_name, type, param.dentry_name,
+ param.file_name);
+ else
+ ERROR("Sub-partition dir operation failed.\n");
+
+ return ret;
+}
+
+static enum ifwi_ret ifwi_add(void)
+{
+ if (!param.file_name) {
+ ERROR("%s: -f option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ if (!param.subpart_name) {
+ ERROR("%s: -n option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ int type = find_type_by_name(param.subpart_name);
+
+ if (type == -1)
+ return COMMAND_ERR;
+
+ const struct subpart_info *curr_subpart = &subparts[type];
+
+ if (curr_subpart->attr & AUTO_GENERATED) {
+ ERROR("Cannot add auto-generated sub-partitions.\n");
+ return COMMAND_ERR;
+ }
+
+ if (buffer_size(&ifwi_image.subpart_buf[type])) {
+ ERROR("Image already contains sub-partition %s(%d).\n",
+ param.subpart_name, type);
+ return COMMAND_ERR;
+ }
+
+ if (param.dir_ops)
+ return ifwi_dir_add(type);
+
+ return ifwi_raw_add(type);
+}
+
+static enum ifwi_ret ifwi_delete(void)
+{
+ if (!param.subpart_name) {
+ ERROR("%s: -n option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ int type = find_type_by_name(param.subpart_name);
+
+ if (type == -1)
+ return COMMAND_ERR;
+
+ const struct subpart_info *curr_subpart = &subparts[type];
+
+ if (curr_subpart->attr & AUTO_GENERATED) {
+ ERROR("Cannot delete auto-generated sub-partitions.\n");
+ return COMMAND_ERR;
+ }
+
+ if (buffer_size(&ifwi_image.subpart_buf[type]) == 0) {
+ printf("Image does not contain sub-partition %s(%d).\n",
+ param.subpart_name, type);
+ return NO_ACTION_REQUIRED;
+ }
+
+ buffer_delete(&ifwi_image.subpart_buf[type]);
+ printf("Sub-Partition %s(%d) deleted.\n", subparts[type].name, type);
+ return REPACK_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_dir_extract(int type)
+{
+ if (!(subparts[type].attr & CONTAINS_DIR)) {
+ ERROR("Sub-Partition %s(%d) does not support dir ops.\n",
+ subparts[type].name, type);
+ return COMMAND_ERR;
+ }
+
+ if (!param.dentry_name) {
+ ERROR("%s: -e option required.\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ struct buffer subpart_dir_buff;
+
+ parse_subpart_dir(&subpart_dir_buff, &ifwi_image.subpart_buf[type],
+ subparts[type].name);
+
+ uint32_t i;
+ struct subpart_dir *s = buffer_get(&subpart_dir_buff);
+
+ for (i = 0; i < s->h.num_entries; i++) {
+ if (!strncmp((char *)s->e[i].name, param.dentry_name,
+ sizeof(s->e[i].name)))
+ break;
+ }
+
+ if (i == s->h.num_entries) {
+ ERROR("Entry %s not found in subpartition for %s.\n",
+ param.dentry_name, param.subpart_name);
+ exit(-1);
+ }
+
+ struct buffer dst;
+
+ DEBUG("Splicing buffer at 0x%x size 0x%x\n", s->e[i].offset,
+ s->e[i].length);
+ buffer_splice(&dst, &ifwi_image.subpart_buf[type], s->e[i].offset,
+ s->e[i].length);
+
+ if (buffer_write_file(&dst, param.file_name))
+ return COMMAND_ERR;
+
+ printf("Sub-Partition %s(%d), entry(%s) stored in %s.\n",
+ param.subpart_name, type, param.dentry_name, param.file_name);
+
+ return NO_ACTION_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_raw_extract(int type)
+{
+ if (buffer_write_file(&ifwi_image.subpart_buf[type], param.file_name))
+ return COMMAND_ERR;
+
+ printf("Sub-Partition %s(%d) stored in %s.\n", param.subpart_name, type,
+ param.file_name);
+
+ return NO_ACTION_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_extract(void)
+{
+ if (!param.file_name) {
+ ERROR("%s: -f option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ if (!param.subpart_name) {
+ ERROR("%s: -n option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ int type = find_type_by_name(param.subpart_name);
+
+ if (type == -1)
+ return COMMAND_ERR;
+
+ if (type == S_BPDT_TYPE) {
+ INFO("Tool does not support raw extract for %s\n",
+ param.subpart_name);
+ return NO_ACTION_REQUIRED;
+ }
+
+ if (buffer_size(&ifwi_image.subpart_buf[type]) == 0) {
+ ERROR("Image does not contain sub-partition %s(%d).\n",
+ param.subpart_name, type);
+ return COMMAND_ERR;
+ }
+
+ INFO("Extracting sub-partition %s(%d).\n", param.subpart_name, type);
+ if (param.dir_ops)
+ return ifwi_dir_extract(type);
+
+ return ifwi_raw_extract(type);
+}
+
+static enum ifwi_ret ifwi_print(void)
+{
+ verbose += 2;
+
+ struct bpdt *b = buffer_get(&ifwi_image.bpdt);
+
+ bpdt_print_header(&b->h, "BPDT");
+ bpdt_print_entries(&b->e[0], b->h.descriptor_count, "BPDT");
+
+ b = buffer_get(&ifwi_image.subpart_buf[S_BPDT_TYPE]);
+ bpdt_print_header(&b->h, "S-BPDT");
+ bpdt_print_entries(&b->e[0], b->h.descriptor_count, "S-BPDT");
+
+ if (param.dir_ops == 0) {
+ verbose -= 2;
+ return NO_ACTION_REQUIRED;
+ }
+
+ int i;
+ struct buffer subpart_dir_buf;
+
+ for (i = 0; i < MAX_SUBPARTS ; i++) {
+ if (!(subparts[i].attr & CONTAINS_DIR) ||
+ (buffer_size(&ifwi_image.subpart_buf[i]) == 0))
+ continue;
+
+ parse_subpart_dir(&subpart_dir_buf, &ifwi_image.subpart_buf[i],
+ subparts[i].name);
+ buffer_delete(&subpart_dir_buf);
+ }
+
+ verbose -= 2;
+
+ return NO_ACTION_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_raw_replace(int type)
+{
+ buffer_delete(&ifwi_image.subpart_buf[type]);
+ return ifwi_raw_add(type);
+}
+
+static enum ifwi_ret ifwi_dir_replace(int type)
+{
+ if (!(subparts[type].attr & CONTAINS_DIR)) {
+ ERROR("Sub-Partition %s(%d) does not support dir ops.\n",
+ subparts[type].name, type);
+ return COMMAND_ERR;
+ }
+
+ if (!param.dentry_name) {
+ ERROR("%s: -e option required.\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ struct buffer subpart_dir_buf;
+
+ parse_subpart_dir(&subpart_dir_buf, &ifwi_image.subpart_buf[type],
+ subparts[type].name);
+
+ uint32_t i;
+ struct subpart_dir *s = buffer_get(&subpart_dir_buf);
+
+ for (i = 0; i < s->h.num_entries; i++) {
+ if (!strcmp((char *)s->e[i].name, param.dentry_name))
+ break;
+ }
+
+ if (i == s->h.num_entries) {
+ ERROR("Entry %s not found in subpartition for %s.\n",
+ param.dentry_name, param.subpart_name);
+ exit(-1);
+ }
+
+ struct buffer b;
+
+ if (buffer_from_file(&b, param.file_name)) {
+ ERROR("Failed to read %s\n", param.file_name);
+ exit(-1);
+ }
+
+ struct buffer dst;
+ size_t dst_size = buffer_size(&ifwi_image.subpart_buf[type]) +
+ buffer_size(&b) - s->e[i].length;
+ size_t subpart_start = s->e[i].offset;
+ size_t subpart_end = s->e[i].offset + s->e[i].length;
+
+ alloc_buffer(&dst, dst_size, ifwi_image.subpart_buf[type].name);
+
+ uint8_t *src_data = buffer_get(&ifwi_image.subpart_buf[type]);
+ uint8_t *dst_data = buffer_get(&dst);
+ size_t curr_offset = 0;
+
+ /* Copy data before the sub-partition entry */
+ memcpy(dst_data + curr_offset, src_data, subpart_start);
+ curr_offset += subpart_start;
+
+ /* Copy sub-partition entry */
+ memcpy(dst_data + curr_offset, buffer_get(&b), buffer_size(&b));
+ curr_offset += buffer_size(&b);
+
+ /* Copy remaining data */
+ memcpy(dst_data + curr_offset, src_data + subpart_end,
+ buffer_size(&ifwi_image.subpart_buf[type]) - subpart_end);
+
+ /* Update sub-partition buffer */
+ int offset = s->e[i].offset;
+
+ buffer_delete(&ifwi_image.subpart_buf[type]);
+ ifwi_image.subpart_buf[type] = dst;
+
+ /* Update length of entry in the subpartition */
+ s->e[i].length = buffer_size(&b);
+ buffer_delete(&b);
+
+ /* Adjust offsets of affected entries in subpartition */
+ offset = s->e[i].offset - offset;
+ for (; i < s->h.num_entries; i++)
+ s->e[i].offset += offset;
+
+ /* Re-calculate checksum */
+ s->h.checksum = calc_checksum(s);
+
+ /* Convert members to litte-endian */
+ subpart_dir_fixup_write_buffer(&subpart_dir_buf);
+
+ memcpy(dst_data, buffer_get(&subpart_dir_buf),
+ buffer_size(&subpart_dir_buf));
+
+ buffer_delete(&subpart_dir_buf);
+
+ printf("Sub-partition %s(%d) entry %s replaced from file %s.\n",
+ param.subpart_name, type, param.dentry_name, param.file_name);
+
+ return REPACK_REQUIRED;
+}
+
+static enum ifwi_ret ifwi_replace(void)
+{
+ if (!param.file_name) {
+ ERROR("%s: -f option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ if (!param.subpart_name) {
+ ERROR("%s: -n option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ int type = find_type_by_name(param.subpart_name);
+
+ if (type == -1)
+ return COMMAND_ERR;
+
+ const struct subpart_info *curr_subpart = &subparts[type];
+
+ if (curr_subpart->attr & AUTO_GENERATED) {
+ ERROR("Cannot replace auto-generated sub-partitions.\n");
+ return COMMAND_ERR;
+ }
+
+ if (buffer_size(&ifwi_image.subpart_buf[type]) == 0) {
+ ERROR("Image does not contain sub-partition %s(%d).\n",
+ param.subpart_name, type);
+ return COMMAND_ERR;
+ }
+
+ if (param.dir_ops)
+ return ifwi_dir_replace(type);
+
+ return ifwi_raw_replace(type);
+}
+
+static enum ifwi_ret ifwi_create(void)
+{
+ /*
+ * Create peels off any non-IFWI content present in the input buffer and
+ * creates output file with only the IFWI present.
+ */
+
+ if (!param.file_name) {
+ ERROR("%s: -f option required\n", __func__);
+ return COMMAND_ERR;
+ }
+
+ /* Peel off any non-IFWI prefix */
+ buffer_seek(&ifwi_image.input_buff,
+ ifwi_image.input_ifwi_start_offset);
+ /* Peel off any non-IFWI suffix */
+ buffer_set_size(&ifwi_image.input_buff,
+ ifwi_image.input_ifwi_end_offset -
+ ifwi_image.input_ifwi_start_offset);
+
+ /*
+ * Adjust start and end offset of IFWI now that non-IFWI prefix is gone.
+ */
+ ifwi_image.input_ifwi_end_offset -= ifwi_image.input_ifwi_start_offset;
+ ifwi_image.input_ifwi_start_offset = 0;
+
+ param.image_name = param.file_name;
+
+ return REPACK_REQUIRED;
+}
+
+struct command {
+ const char *name;
+ const char *optstring;
+ enum ifwi_ret (*function)(void);
+};
+
+static const struct command commands[] = {
+ {"add", "f:n:e:dvh?", ifwi_add},
+ {"create", "f:vh?", ifwi_create},
+ {"delete", "f:n:vh?", ifwi_delete},
+ {"extract", "f:n:e:dvh?", ifwi_extract},
+ {"print", "dh?", ifwi_print},
+ {"replace", "f:n:e:dvh?", ifwi_replace},
+};
+
+static struct option long_options[] = {
+ {"subpart_dentry", required_argument, 0, 'e'},
+ {"file", required_argument, 0, 'f'},
+ {"help", required_argument, 0, 'h'},
+ {"name", required_argument, 0, 'n'},
+ {"dir_ops", no_argument, 0, 'd'},
+ {"verbose", no_argument, 0, 'v'},
+ {NULL, 0, 0, 0 }
+};
+
+static void usage(const char *name)
+{
+ printf("ifwitool: Utility for IFWI manipulation\n\n"
+ "USAGE:\n"
+ " %s [-h]\n"
+ " %s FILE COMMAND [PARAMETERS]\n\n"
+ "COMMANDs:\n"
+ " add -f FILE -n NAME [-d -e ENTRY]\n"
+ " create -f FILE\n"
+ " delete -n NAME\n"
+ " extract -f FILE -n NAME [-d -e ENTRY]\n"
+ " print [-d]\n"
+ " replace -f FILE -n NAME [-d -e ENTRY]\n"
+ "OPTIONs:\n"
+ " -f FILE : File to read/write/create/extract\n"
+ " -d : Perform directory operation\n"
+ " -e ENTRY: Name of directory entry to operate on\n"
+ " -v : Verbose level\n"
+ " -h : Help message\n"
+ " -n NAME : Name of sub-partition to operate on\n",
+ name, name
+ );
+
+ printf("\nNAME should be one of:\n");
+ int i;
+
+ for (i = 0; i < MAX_SUBPARTS; i++)
+ printf("%s(%s)\n", subparts[i].name, subparts[i].readable_name);
+ printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 3) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ param.image_name = argv[1];
+ char *cmd = argv[2];
+
+ optind += 2;
+
+ uint32_t i;
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ if (strcmp(cmd, commands[i].name) != 0)
+ continue;
+
+ int c;
+
+ while (1) {
+ int option_index;
+
+ c = getopt_long(argc, argv, commands[i].optstring,
+ long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ /* Filter out illegal long options */
+ if (!strchr(commands[i].optstring, c)) {
+ ERROR("%s: invalid option -- '%c'\n", argv[0],
+ c);
+ c = '?';
+ }
+
+ switch (c) {
+ case 'n':
+ param.subpart_name = optarg;
+ break;
+ case 'f':
+ param.file_name = optarg;
+ break;
+ case 'd':
+ param.dir_ops = 1;
+ break;
+ case 'e':
+ param.dentry_name = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'h':
+ case '?':
+ usage(argv[0]);
+ return 1;
+ default:
+ break;
+ }
+ }
+
+ if (ifwi_parse()) {
+ ERROR("%s: ifwi parsing failed\n", argv[0]);
+ return 1;
+ }
+
+ enum ifwi_ret ret = commands[i].function();
+
+ if (ret == COMMAND_ERR) {
+ ERROR("%s: failed execution\n", argv[0]);
+ return 1;
+ }
+
+ if (ret == REPACK_REQUIRED)
+ ifwi_repack();
+
+ return 0;
+ }
+
+ ERROR("%s: invalid command\n", argv[0]);
+ return 1;
+}
diff --git a/tools/patman/command.py b/tools/patman/command.py
index 14edcda..16299f3 100644
--- a/tools/patman/command.py
+++ b/tools/patman/command.py
@@ -108,8 +108,8 @@
return result
def Output(*cmd, **kwargs):
- raise_on_error = kwargs.get('raise_on_error', True)
- return RunPipe([cmd], capture=True, raise_on_error=raise_on_error).stdout
+ kwargs['raise_on_error'] = kwargs.get('raise_on_error', True)
+ return RunPipe([cmd], capture=True, **kwargs).stdout
def OutputOneLine(*cmd, **kwargs):
raise_on_error = kwargs.pop('raise_on_error', True)
diff --git a/tools/patman/test_util.py b/tools/patman/test_util.py
index ea36cd1..09f258c 100644
--- a/tools/patman/test_util.py
+++ b/tools/patman/test_util.py
@@ -46,9 +46,10 @@
glob_list = []
glob_list += exclude_list
glob_list += ['*libfdt.py', '*site-packages*', '*dist-packages*']
+ test_cmd = 'test' if 'binman.py' in prog else '-t'
cmd = ('PYTHONPATH=$PYTHONPATH:%s/sandbox_spl/tools %s-coverage run '
- '--omit "%s" %s -P1 -t' % (build_dir, PYTHON, ','.join(glob_list),
- prog))
+ '--omit "%s" %s %s -P1' % (build_dir, PYTHON, ','.join(glob_list),
+ prog, test_cmd))
os.system(cmd)
stdout = command.Output('%s-coverage' % PYTHON, 'report')
lines = stdout.splitlines()
@@ -57,6 +58,7 @@
test_set = set([os.path.splitext(os.path.basename(line.split()[0]))[0]
for line in lines if '/etype/' in line])
missing_list = required
+ missing_list.discard('__init__')
missing_list.difference_update(test_set)
if missing_list:
print('Missing tests for %s' % (', '.join(missing_list)))
diff --git a/tools/patman/tools.py b/tools/patman/tools.py
index 8e9f22a..e945b54 100644
--- a/tools/patman/tools.py
+++ b/tools/patman/tools.py
@@ -3,6 +3,8 @@
# Copyright (c) 2016 Google, Inc
#
+from __future__ import print_function
+
import command
import glob
import os
@@ -24,6 +26,8 @@
# Search paths to use for Filename(), used to find files
search_paths = []
+tool_search_paths = []
+
# Tools and the packages that contain them, on debian
packages = {
'lz4': 'liblz4-tool',
@@ -154,26 +158,56 @@
def NotPowerOfTwo(num):
return num and (num & (num - 1))
+def SetToolPaths(toolpaths):
+ """Set the path to search for tools
+
+ Args:
+ toolpaths: List of paths to search for tools executed by Run()
+ """
+ global tool_search_paths
+
+ tool_search_paths = toolpaths
+
-def PathHasFile(fname):
+def PathHasFile(path_spec, fname):
"""Check if a given filename is in the PATH
Args:
+ path_spec: Value of PATH variable to check
fname: Filename to check
Returns:
True if found, False if not
"""
- for dir in os.environ['PATH'].split(':'):
+ for dir in path_spec.split(':'):
if os.path.exists(os.path.join(dir, fname)):
return True
return False
def Run(name, *args, **kwargs):
+ """Run a tool with some arguments
+
+ This runs a 'tool', which is a program used by binman to process files and
+ perhaps produce some output. Tools can be located on the PATH or in a
+ search path.
+
+ Args:
+ name: Command name to run
+ args: Arguments to the tool
+ kwargs: Options to pass to command.run()
+
+ Returns:
+ CommandResult object
+ """
try:
- return command.Run(name, *args, cwd=outdir, capture=True, **kwargs)
+ env = None
+ if tool_search_paths:
+ env = dict(os.environ)
+ env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
+ return command.Run(name, *args, capture=True,
+ capture_stderr=True, env=env, **kwargs)
except:
- if not PathHasFile(name):
- msg = "Plesae install tool '%s'" % name
+ if env and not PathHasFile(env['PATH'], name):
+ msg = "Please install tool '%s'" % name
package = packages.get(name)
if package:
msg += " (e.g. from package '%s')" % package
@@ -342,3 +376,100 @@
if sys.version_info[0] >= 3:
return string.encode('utf-8')
return string
+
+def Compress(indata, algo):
+ """Compress some data using a given algorithm
+
+ Note that for lzma this uses an old version of the algorithm, not that
+ provided by xz.
+
+ This requires 'lz4' and 'lzma_alone' tools. It also requires an output
+ directory to be previously set up, by calling PrepareOutputDir().
+
+ Args:
+ indata: Input data to compress
+ algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
+
+ Returns:
+ Compressed data
+ """
+ if algo == 'none':
+ return indata
+ fname = GetOutputFilename('%s.comp.tmp' % algo)
+ WriteFile(fname, indata)
+ if algo == 'lz4':
+ data = Run('lz4', '--no-frame-crc', '-c', fname, binary=True)
+ # cbfstool uses a very old version of lzma
+ elif algo == 'lzma':
+ outfname = GetOutputFilename('%s.comp.otmp' % algo)
+ Run('lzma_alone', 'e', fname, outfname, '-lc1', '-lp0', '-pb0', '-d8')
+ data = ReadFile(outfname)
+ elif algo == 'gzip':
+ data = Run('gzip', '-c', fname, binary=True)
+ else:
+ raise ValueError("Unknown algorithm '%s'" % algo)
+ return data
+
+def Decompress(indata, algo):
+ """Decompress some data using a given algorithm
+
+ Note that for lzma this uses an old version of the algorithm, not that
+ provided by xz.
+
+ This requires 'lz4' and 'lzma_alone' tools. It also requires an output
+ directory to be previously set up, by calling PrepareOutputDir().
+
+ Args:
+ indata: Input data to decompress
+ algo: Algorithm to use ('none', 'gzip', 'lz4' or 'lzma')
+
+ Returns:
+ Compressed data
+ """
+ if algo == 'none':
+ return indata
+ fname = GetOutputFilename('%s.decomp.tmp' % algo)
+ with open(fname, 'wb') as fd:
+ fd.write(indata)
+ if algo == 'lz4':
+ data = Run('lz4', '-dc', fname, binary=True)
+ elif algo == 'lzma':
+ outfname = GetOutputFilename('%s.decomp.otmp' % algo)
+ Run('lzma_alone', 'd', fname, outfname)
+ data = ReadFile(outfname)
+ elif algo == 'gzip':
+ data = Run('gzip', '-cd', fname, binary=True)
+ else:
+ raise ValueError("Unknown algorithm '%s'" % algo)
+ return data
+
+CMD_CREATE, CMD_DELETE, CMD_ADD, CMD_REPLACE, CMD_EXTRACT = range(5)
+
+IFWITOOL_CMDS = {
+ CMD_CREATE: 'create',
+ CMD_DELETE: 'delete',
+ CMD_ADD: 'add',
+ CMD_REPLACE: 'replace',
+ CMD_EXTRACT: 'extract',
+ }
+
+def RunIfwiTool(ifwi_file, cmd, fname=None, subpart=None, entry_name=None):
+ """Run ifwitool with the given arguments:
+
+ Args:
+ ifwi_file: IFWI file to operation on
+ cmd: Command to execute (CMD_...)
+ fname: Filename of file to add/replace/extract/create (None for
+ CMD_DELETE)
+ subpart: Name of sub-partition to operation on (None for CMD_CREATE)
+ entry_name: Name of directory entry to operate on, or None if none
+ """
+ args = ['ifwitool', ifwi_file]
+ args.append(IFWITOOL_CMDS[cmd])
+ if fname:
+ args += ['-f', fname]
+ if subpart:
+ args += ['-n', subpart]
+ if entry_name:
+ args += ['-d', '-e', entry_name]
+ Run(*args)
diff --git a/tools/patman/tout.py b/tools/patman/tout.py
index 4957c7a..15acce2 100644
--- a/tools/patman/tout.py
+++ b/tools/patman/tout.py
@@ -131,13 +131,21 @@
"""
_Output(3, msg)
+def Detail(msg):
+ """Display a detailed message
+
+ Args:
+ msg; Message to display.
+ """
+ _Output(4, msg)
+
def Debug(msg):
"""Display a debug message
Args:
msg; Message to display.
"""
- _Output(4, msg)
+ _Output(5, msg)
def UserOutput(msg):
"""Display a message regardless of the current output level.