From 90c08fa038451d6d7b7d8711bfd829b61d64c490 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Mon, 11 Jun 2018 13:07:09 -0600 Subject: [PATCH 01/40] fdt: Add device tree memory bindings Support a default memory bank, specified in reg, as well as board-specific memory banks in subtree board-id nodes. This allows memory information to be provided in the device tree, rather than hard-coded in, which will make it simpler to handle similar devices with different memory banks, as the board-id values or masks can be used to match devices. Signed-off-by: Michael Pratt Signed-off-by: Simon Glass Reviewed-by: Vadim Bendebury --- doc/device-tree-bindings/memory/memory.txt | 67 ++++++++++++++++++ include/asm-generic/global_data.h | 1 + include/fdt_support.h | 10 +++ include/fdtdec.h | 38 +++++++++- lib/fdtdec.c | 109 +++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 doc/device-tree-bindings/memory/memory.txt diff --git a/doc/device-tree-bindings/memory/memory.txt b/doc/device-tree-bindings/memory/memory.txt new file mode 100644 index 0000000..321894e --- /dev/null +++ b/doc/device-tree-bindings/memory/memory.txt @@ -0,0 +1,67 @@ +* Memory binding + +The memory binding for U-Boot is as in the ePAPR with the following additions: + +Optional subnodes can be used defining the memory layout for different board +ID masks. To match a set of board ids, a board-id node may define match-mask +and match-value ints to define a mask to apply to the board id, and the value +that the result should have for the match to be considered valid. The mask +defaults to -1, meaning that the value must fully match the board id. + +If subnodes are present, then the /memory node must define these properties: + +- #address-cells: should be 1. +- #size-cells: should be 0. + +Each subnode must define + + reg - board ID or mask for this subnode + memory-banks - list of memory banks in the same format as normal + +Each subnode may optionally define: + + match-mask - A mask to apply to the board id. This must be accompanied by + match-value. + match-value - The required resulting value of the board id mask for the given + node to be considered a match. + auto-size - Indicates that the value given for a bank is the maximum size, + each bank is probed to determine its actual size, which may be + smaller + + +The board id determination is up to the vendor and is not defined by this +binding. + +Example: + +memory { + #address-cells = <1>; + #size-cells = <1>; + reg = <0x20000000 0x20000000 + 0x40000000 0x20000000 + 0x60000000 0x20000000 + 0x80000000 0x20000000>; + auto-size; + board-id@0 { + match-value = <17>; + reg = <0x20000000 0x20000000 + 0x40000000 0x20000000>; + }; + board-id@1 { + match-mask = <2>; + match-value = <2>; + reg = <0x20000000 0x20000000 + 0x40000000 0x20000000 + 0x60000000 0x20000000 + 0x80000000 0x20000000 + 0xa0000000 0x20000000 + 0xc0000000 0x20000000 + 0xe0000000 0x20000000>; + }; +}; + + +This shows a system with the following properties: +* Default of 2GB of memory, auto-sized, so could be smaller +* 3.5GB of memory (with no auto-size) if (board id & 2) is 2 +* 1GB of memory (with no auto-size) if board id is 17. diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h index 2d451f8..0fd4900 100644 --- a/include/asm-generic/global_data.h +++ b/include/asm-generic/global_data.h @@ -52,6 +52,7 @@ typedef struct global_data { unsigned long env_has_init; /* Bitmask of boolean of struct env_location offsets */ int env_load_location; + unsigned long ram_base; /* Base address of RAM used by U-Boot */ unsigned long ram_top; /* Top address of RAM used by U-Boot */ unsigned long relocaddr; /* Start address of U-Boot in RAM */ phys_size_t ram_size; /* RAM size */ diff --git a/include/fdt_support.h b/include/fdt_support.h index e6c43ea..a9a0078 100644 --- a/include/fdt_support.h +++ b/include/fdt_support.h @@ -283,6 +283,16 @@ int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width, int fdt_overlay_apply_verbose(void *fdt, void *fdto); +/** + * fdt_get_cells_len() - Get the length of a type of cell in top-level nodes + * + * Returns the length of the cell type in bytes (4 or 8). + * + * @blob: Pointer to device tree blob + * @nr_cells_name: Name to lookup, e.g. "#address-cells" + */ +int fdt_get_cells_len(const void *blob, char *nr_cells_name); + #endif /* ifdef CONFIG_OF_LIBFDT */ #ifdef USE_HOSTCC diff --git a/include/fdtdec.h b/include/fdtdec.h index c15b2a0..3321055 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -41,6 +41,8 @@ struct fdt_memory { fdt_addr_t end; }; +struct bd_info; + #ifdef CONFIG_SPL_BUILD #define SPL_BUILD 1 #else @@ -993,6 +995,40 @@ int fdtdec_setup(void); * Called when CONFIG_OF_BOARD is defined, or if CONFIG_OF_SEPARATE is defined * and the board implements it. */ -void *board_fdt_blob_setup(void); + +/* + * Decode the size of memory + * + * RAM size is normally set in a /memory node and consists of a list of + * (base, size) cells in the 'reg' property. This information is used to + * determine the total available memory as well as the address and size + * of each bank. + * + * Optionally the memory configuration can vary depending on a board id, + * typically read from strapping resistors or an EEPROM on the board. + * + * Finally, memory size can be detected (within certain limits) by probing + * the available memory. It is safe to do so within the limits provides by + * the board's device tree information. This makes it possible to produce + * boards with different memory sizes, where the device tree specifies the + * maximum memory configuration, and the smaller memory configuration is + * probed. + * + * This function decodes that information, returning the memory base address, + * size and bank information. See the memory.txt binding for full + * documentation. + * + * @param blob Device tree blob + * @param area Name of node to check (NULL means "/memory") + * @param board_id Board ID to look up + * @param basep Returns base address of first memory bank (NULL to + * ignore) + * @param sizep Returns total memory size (NULL to ignore) + * @param bd Updated with the memory bank information (NULL to skip) + * @return 0 if OK, -ve on error + */ +int fdtdec_decode_ram_size(const void *blob, const char *area, int board_id, + phys_addr_t *basep, phys_size_t *sizep, + struct bd_info *bd); #endif diff --git a/lib/fdtdec.c b/lib/fdtdec.c index f4e8dbf..fc92082 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1350,4 +1351,112 @@ int fdtdec_setup(void) return fdtdec_prepare_fdt(); } +#ifdef CONFIG_NR_DRAM_BANKS +int fdtdec_decode_ram_size(const void *blob, const char *area, int board_id, + phys_addr_t *basep, phys_size_t *sizep, bd_t *bd) +{ + int addr_cells, size_cells; + const u32 *cell, *end; + u64 total_size, size, addr; + int node, child; + bool auto_size; + int bank; + int len; + + debug("%s: board_id=%d\n", __func__, board_id); + if (!area) + area = "/memory"; + node = fdt_path_offset(blob, area); + if (node < 0) { + debug("No %s node found\n", area); + return -ENOENT; + } + + cell = fdt_getprop(blob, node, "reg", &len); + if (!cell) { + debug("No reg property found\n"); + return -ENOENT; + } + + addr_cells = fdt_address_cells(blob, node); + size_cells = fdt_size_cells(blob, node); + + /* Check the board id and mask */ + for (child = fdt_first_subnode(blob, node); + child >= 0; + child = fdt_next_subnode(blob, child)) { + int match_mask, match_value; + + match_mask = fdtdec_get_int(blob, child, "match-mask", -1); + match_value = fdtdec_get_int(blob, child, "match-value", -1); + + if (match_value >= 0 && + ((board_id & match_mask) == match_value)) { + /* Found matching mask */ + debug("Found matching mask %d\n", match_mask); + node = child; + cell = fdt_getprop(blob, node, "reg", &len); + if (!cell) { + debug("No memory-banks property found\n"); + return -EINVAL; + } + break; + } + } + /* Note: if no matching subnode was found we use the parent node */ + + if (bd) { + memset(bd->bi_dram, '\0', sizeof(bd->bi_dram[0]) * + CONFIG_NR_DRAM_BANKS); + } + + auto_size = fdtdec_get_bool(blob, node, "auto-size"); + + total_size = 0; + end = cell + len / 4 - addr_cells - size_cells; + debug("cell at %p, end %p\n", cell, end); + for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { + if (cell > end) + break; + addr = 0; + if (addr_cells == 2) + addr += (u64)fdt32_to_cpu(*cell++) << 32UL; + addr += fdt32_to_cpu(*cell++); + if (bd) + bd->bi_dram[bank].start = addr; + if (basep && !bank) + *basep = (phys_addr_t)addr; + + size = 0; + if (size_cells == 2) + size += (u64)fdt32_to_cpu(*cell++) << 32UL; + size += fdt32_to_cpu(*cell++); + + if (auto_size) { + u64 new_size; + + debug("Auto-sizing %" PRIx64 ", size %" PRIx64 ": ", + addr, size); + new_size = get_ram_size((long *)(uintptr_t)addr, size); + if (new_size == size) { + debug("OK\n"); + } else { + debug("sized to %" PRIx64 "\n", new_size); + size = new_size; + } + } + + if (bd) + bd->bi_dram[bank].size = size; + total_size += size; + } + + debug("Memory size %" PRIu64 "\n", total_size); + if (sizep) + *sizep = (phys_size_t)total_size; + + return 0; +} +#endif /* CONFIG_NR_DRAM_BANKS */ + #endif /* !USE_HOSTCC */ From 7e5196c409f17091f2aeca144c6d76750df81cc4 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:10 -0600 Subject: [PATCH 02/40] dm: core: Add ofnode function to read a 64-bit int We have a 32-bit version of this function. Add a 64-bit version as well so we can easily read 64-bit ints from the device tree. Signed-off-by: Simon Glass --- drivers/core/of_access.c | 20 ++++++++++++++++++++ drivers/core/ofnode.c | 32 ++++++++++++++++++++++++++++++++ include/dm/of_access.h | 16 ++++++++++++++++ include/dm/ofnode.h | 10 ++++++++++ 4 files changed, 78 insertions(+) diff --git a/drivers/core/of_access.c b/drivers/core/of_access.c index 9a50f55..0729dfc 100644 --- a/drivers/core/of_access.c +++ b/drivers/core/of_access.c @@ -457,6 +457,26 @@ int of_read_u32_array(const struct device_node *np, const char *propname, return 0; } +int of_read_u64(const struct device_node *np, const char *propname, u64 *outp) +{ + const __be64 *val; + + debug("%s: %s: ", __func__, propname); + if (!np) + return -EINVAL; + val = of_find_property_value_of_size(np, propname, sizeof(*outp)); + if (IS_ERR(val)) { + debug("(not found)\n"); + return PTR_ERR(val); + } + + *outp = be64_to_cpup(val); + debug("%#llx (%lld)\n", (unsigned long long)*outp, + (unsigned long long)*outp); + + return 0; +} + int of_property_match_string(const struct device_node *np, const char *propname, const char *string) { diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index 3cf3205..b2b02e4 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -55,6 +55,38 @@ int ofnode_read_s32_default(ofnode node, const char *propname, s32 def) return def; } +int ofnode_read_u64(ofnode node, const char *propname, u64 *outp) +{ + const fdt64_t *cell; + int len; + + assert(ofnode_valid(node)); + debug("%s: %s: ", __func__, propname); + + if (ofnode_is_np(node)) + return of_read_u64(ofnode_to_np(node), propname, outp); + + cell = fdt_getprop(gd->fdt_blob, ofnode_to_offset(node), propname, + &len); + if (!cell || len < sizeof(*cell)) { + debug("(not found)\n"); + return -EINVAL; + } + *outp = fdt64_to_cpu(cell[0]); + debug("%#llx (%lld)\n", (unsigned long long)*outp, + (unsigned long long)*outp); + + return 0; +} + +int ofnode_read_u64_default(ofnode node, const char *propname, u64 def) +{ + assert(ofnode_valid(node)); + ofnode_read_u64(node, propname, &def); + + return def; +} + bool ofnode_read_bool(ofnode node, const char *propname) { const void *prop; diff --git a/include/dm/of_access.h b/include/dm/of_access.h index 74f0606..dd1abb8 100644 --- a/include/dm/of_access.h +++ b/include/dm/of_access.h @@ -219,6 +219,22 @@ struct device_node *of_find_node_by_phandle(phandle handle); int of_read_u32(const struct device_node *np, const char *propname, u32 *outp); /** + * of_read_u64() - Find and read a 64-bit integer from a property + * + * Search for a property in a device node and read a 64-bit value from + * it. + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @outp: pointer to return value, modified only if return value is 0. + * + * @return 0 on success, -EINVAL if the property does not exist, + * -ENODATA if property does not have a value, and -EOVERFLOW if the + * property data isn't large enough. + */ +int of_read_u64(const struct device_node *np, const char *propname, u64 *outp); + +/** * of_read_u32_array() - Find and read an array of 32 bit integers * * Search for a property in a device node and read 32-bit value(s) from diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 5af6b7e..dbb4273 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -237,6 +237,16 @@ int ofnode_read_u32_default(ofnode ref, const char *propname, u32 def); int ofnode_read_s32_default(ofnode node, const char *propname, s32 def); /** + * ofnode_read_u64_default() - Read a 64-bit integer from a property + * + * @ref: valid node reference to read property from + * @propname: name of the property to read from + * @def: default value to return if the property has no value + * @return property value, or @def if not found + */ +int ofnode_read_u64_default(ofnode node, const char *propname, u64 def); + +/** * ofnode_read_string() - Read a string from a property * * @ref: valid node reference to read property from From fbe8d033fcc64eef90d3e9642545804a0ab9b74f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:11 -0600 Subject: [PATCH 03/40] dm: core: Fix a few ofnode function comments Tidy up three return-value errors. Signed-off-by: Simon Glass --- include/dm/ofnode.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index dbb4273..85cb87b 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -262,6 +262,7 @@ const char *ofnode_read_string(ofnode node, const char *propname); * @propname: name of the property to read * @out_values: pointer to return value, modified only if return value is 0 * @sz: number of array elements to read + * @return 0 if OK, -ve on error * * Search for a property in a device node and read 32-bit value(s) from * it. Returns 0 on success, -EINVAL if the property does not exist, @@ -490,6 +491,7 @@ ofnode ofnode_path(const char *path); * This looks for a property within the /chosen node and returns its value * * @propname: Property name to look for + * @return property value if found, else NULL */ const char *ofnode_get_chosen_prop(const char *propname); @@ -645,7 +647,7 @@ int ofnode_read_simple_size_cells(ofnode node); * new platforms. * * @node: node to check - * @eturns true if node is needed in SPL/TL, false otherwise + * @return true if node is needed in SPL/TL, false otherwise */ bool ofnode_pre_reloc(ofnode node); From c98ad4434c1a680915d10e5696e6bee9c81044d5 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:12 -0600 Subject: [PATCH 04/40] dm: core: Add comments to ofnode_read_resource() functoins These functions are missing comments. Add some. Signed-off-by: Simon Glass --- include/dm/ofnode.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 85cb87b..61c4231 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -651,7 +651,30 @@ int ofnode_read_simple_size_cells(ofnode node); */ bool ofnode_pre_reloc(ofnode node); +/** + * ofnode_read_resource() - Read a resource from a node + * + * Read resource information from a node at the given index + * + * @node: Node to read from + * @index: Index of resource to read (0 = first) + * @res: Returns resource that was read, on success + * @return 0 if OK, -ve on error + */ int ofnode_read_resource(ofnode node, uint index, struct resource *res); + +/** + * ofnode_read_resource_byname() - Read a resource from a node by name + * + * Read resource information from a node matching the given name. This uses a + * 'reg-names' string list property with the names matching the associated + * 'reg' property list. + * + * @node: Node to read from + * @name: Name of resource to read + * @res: Returns resource that was read, on success + * @return 0 if OK, -ve on error + */ int ofnode_read_resource_byname(ofnode node, const char *name, struct resource *res); From c60f671b65a8b336c3533fcf0f0ee45dff287ff7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:13 -0600 Subject: [PATCH 05/40] dm: core: Add a way to find an ofnode by compatible string Add an ofnode_by_compatible() to allow iterating through ofnodes with a given compatible string. Signed-off-by: Simon Glass --- drivers/core/ofnode.c | 12 ++++++++++++ include/dm/ofnode.h | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index b2b02e4..2937539 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -729,3 +729,15 @@ int ofnode_device_is_compatible(ofnode node, const char *compat) ofnode_to_offset(node), compat); } + +ofnode ofnode_by_compatible(ofnode from, const char *compat) +{ + if (of_live_active()) { + return np_to_ofnode(of_find_compatible_node( + (struct device_node *)ofnode_to_np(from), NULL, + compat)); + } else { + return offset_to_ofnode(fdt_node_offset_by_compatible( + gd->fdt_blob, ofnode_to_offset(from), compat)); + } +} diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 61c4231..cd08a7e 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -679,6 +679,17 @@ int ofnode_read_resource_byname(ofnode node, const char *name, struct resource *res); /** + * ofnode_by_compatible() - Find the next compatible node + * + * Find the next node after @from that is compatible with @compat + * + * @from: ofnode to start from (use ofnode_null() to start at the beginning) + * @compat: Compatible string to match + * @return ofnode found, or ofnode_null() if none + */ +ofnode ofnode_by_compatible(ofnode from, const char *compat); + +/** * ofnode_for_each_subnode() - iterate over all subnodes of a parent * * @node: child node (ofnode, lvalue) From b616cef97aa8562a8c08558505675e255b5347cc Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:14 -0600 Subject: [PATCH 06/40] log: Add a way to log a return value with a message It is sometimes useful to show a message when logging an error return value, perhaps to add a few details about the problem. Add a function to support this. Signed-off-by: Simon Glass --- include/log.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/log.h b/include/log.h index 3e99d6e..653fb8d 100644 --- a/include/log.h +++ b/include/log.h @@ -166,8 +166,16 @@ void __assert_fail(const char *assertion, const char *file, unsigned int line, log(LOG_CATEGORY, LOGL_ERR, "returning err=%d\n", __ret); \ __ret; \ }) +#define log_msg_ret(_msg, _ret) ({ \ + int __ret = (_ret); \ + if (__ret < 0) \ + log(LOG_CATEGORY, LOGL_ERR, "%s: returning err=%d\n", _msg, \ + __ret); \ + __ret; \ + }) #else #define log_ret(_ret) (_ret) +#define log_msg_ret(_ret) (_ret) #endif /** From d677b00cb62a4cf4d4a24468f218581b4f57c2fe Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:15 -0600 Subject: [PATCH 07/40] dm: core: Add a way to bind a device by ofnode Add a new device_bind_ofnode() function which can bind a device given its ofnode. This allows binding devices more easily with livetree nodes. Signed-off-by: Simon Glass --- drivers/core/device.c | 8 ++++++++ include/dm/device-internal.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/drivers/core/device.c b/drivers/core/device.c index e048e1a..d5f5fc3 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -230,6 +230,14 @@ int device_bind(struct udevice *parent, const struct driver *drv, offset_to_ofnode(of_offset), 0, devp); } +int device_bind_ofnode(struct udevice *parent, const struct driver *drv, + const char *name, void *platdata, ofnode node, + struct udevice **devp) +{ + return device_bind_common(parent, drv, name, platdata, 0, node, 0, + devp); +} + int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, const struct driver_info *info, struct udevice **devp) { diff --git a/include/dm/device-internal.h b/include/dm/device-internal.h index 5a4d50c..f4af154 100644 --- a/include/dm/device-internal.h +++ b/include/dm/device-internal.h @@ -40,6 +40,10 @@ int device_bind(struct udevice *parent, const struct driver *drv, const char *name, void *platdata, int of_offset, struct udevice **devp); +int device_bind_ofnode(struct udevice *parent, const struct driver *drv, + const char *name, void *platdata, ofnode node, + struct udevice **devp); + /** * device_bind_with_driver_data() - Create a device and bind it to a driver * From 008dcddf9937bd2576f98b48eb5bf0f60ad36014 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:16 -0600 Subject: [PATCH 08/40] dm: spi: Update sandbox SPI emulation driver to use ofnode Update the parameters sandbox_sf_bind_emul to support livetree. Signed-off-by: Simon Glass --- drivers/mtd/spi/sandbox.c | 9 +++++---- include/spi_flash.h | 2 +- test/dm/spi.c | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/spi/sandbox.c b/drivers/mtd/spi/sandbox.c index f23c0e1..1b6c028 100644 --- a/drivers/mtd/spi/sandbox.c +++ b/drivers/mtd/spi/sandbox.c @@ -556,7 +556,7 @@ static int sandbox_cmdline_cb_spi_sf(struct sandbox_state *state, SANDBOX_CMDLINE_OPT(spi_sf, 1, "connect a SPI flash: :::"); int sandbox_sf_bind_emul(struct sandbox_state *state, int busnum, int cs, - struct udevice *bus, int of_offset, const char *spec) + struct udevice *bus, ofnode node, const char *spec) { struct udevice *emul; char name[20], *str; @@ -575,7 +575,7 @@ int sandbox_sf_bind_emul(struct sandbox_state *state, int busnum, int cs, str = strdup(name); if (!str) return -ENOMEM; - ret = device_bind(bus, drv, str, NULL, of_offset, &emul); + ret = device_bind_ofnode(bus, drv, str, NULL, node, &emul); if (ret) { free(str); printf("Cannot create emul device for spec '%s' (err=%d)\n", @@ -620,7 +620,8 @@ static int sandbox_sf_bind_bus_cs(struct sandbox_state *state, int busnum, if (ret) return ret; - return sandbox_sf_bind_emul(state, busnum, cs, bus, -1, spec); + return sandbox_sf_bind_emul(state, busnum, cs, bus, ofnode_null(), + spec); } int sandbox_spi_get_emul(struct sandbox_state *state, @@ -638,7 +639,7 @@ int sandbox_spi_get_emul(struct sandbox_state *state, debug("%s: busnum=%u, cs=%u: binding SPI flash emulation: ", __func__, busnum, cs); ret = sandbox_sf_bind_emul(state, busnum, cs, bus, - dev_of_offset(slave), slave->name); + dev_ofnode(slave), slave->name); if (ret) { debug("failed (err=%d)\n", ret); return ret; diff --git a/include/spi_flash.h b/include/spi_flash.h index 2253331..0ec98fb 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -185,7 +185,7 @@ static inline int spi_flash_erase(struct spi_flash *flash, u32 offset, struct sandbox_state; int sandbox_sf_bind_emul(struct sandbox_state *state, int busnum, int cs, - struct udevice *bus, int of_offset, const char *spec); + struct udevice *bus, ofnode node, const char *spec); void sandbox_sf_unbind_emul(struct sandbox_state *state, int busnum, int cs); diff --git a/test/dm/spi.c b/test/dm/spi.c index 252b874..ffd789c 100644 --- a/test/dm/spi.c +++ b/test/dm/spi.c @@ -23,7 +23,7 @@ static int dm_test_spi_find(struct unit_test_state *uts) struct udevice *bus, *dev; const int busnum = 0, cs = 0, mode = 0, speed = 1000000, cs_b = 1; struct spi_cs_info info; - int of_offset; + ofnode node; ut_asserteq(-ENODEV, uclass_find_device_by_seq(UCLASS_SPI, busnum, false, &bus)); @@ -34,7 +34,7 @@ static int dm_test_spi_find(struct unit_test_state *uts) */ ut_asserteq(0, uclass_get_device_by_seq(UCLASS_SPI, busnum, &bus)); ut_assertok(spi_cs_info(bus, cs, &info)); - of_offset = dev_of_offset(info.dev); + node = dev_ofnode(info.dev); device_remove(info.dev, DM_REMOVE_NORMAL); device_unbind(info.dev); @@ -65,7 +65,7 @@ static int dm_test_spi_find(struct unit_test_state *uts) ut_asserteq_ptr(NULL, info.dev); /* Add the emulation and try again */ - ut_assertok(sandbox_sf_bind_emul(state, busnum, cs, bus, of_offset, + ut_assertok(sandbox_sf_bind_emul(state, busnum, cs, bus, node, "name")); ut_assertok(spi_find_bus_and_cs(busnum, cs, &bus, &dev)); ut_assertok(spi_get_bus_and_cs(busnum, cs, speed, mode, @@ -75,7 +75,7 @@ static int dm_test_spi_find(struct unit_test_state *uts) ut_asserteq_ptr(info.dev, slave->dev); /* We should be able to add something to another chip select */ - ut_assertok(sandbox_sf_bind_emul(state, busnum, cs_b, bus, of_offset, + ut_assertok(sandbox_sf_bind_emul(state, busnum, cs_b, bus, node, "name")); ut_assertok(spi_get_bus_and_cs(busnum, cs_b, speed, mode, "spi_flash_std", "name", &bus, &slave)); From 5e0a7341cdda182f310624d5c336fb48be04a703 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:17 -0600 Subject: [PATCH 09/40] dm: core: Update of_read_fmap_entry() for livetree Update this function to take an ofnode so that it can work with livetree. Signed-off-by: Simon Glass --- drivers/core/of_extra.c | 8 ++++---- drivers/misc/cros_ec.c | 4 ++-- include/dm/of_extra.h | 6 ++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/core/of_extra.c b/drivers/core/of_extra.c index c76177c..3243caa 100644 --- a/drivers/core/of_extra.c +++ b/drivers/core/of_extra.c @@ -10,15 +10,15 @@ #include #include -int of_read_fmap_entry(ofnode node, const char *name, - struct fmap_entry *entry) +int ofnode_read_fmap_entry(ofnode node, struct fmap_entry *entry) { const char *prop; u32 reg[2]; if (ofnode_read_u32_array(node, "reg", reg, 2)) { - debug("Node '%s' has bad/missing 'reg' property\n", name); - return -FDT_ERR_NOTFOUND; + debug("Node '%s' has bad/missing 'reg' property\n", + ofnode_get_name(node)); + return -log_ret(ENOENT); } entry->offset = reg[0]; entry->length = reg[1]; diff --git a/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c index 5fd2cd9..6f299d4 100644 --- a/drivers/misc/cros_ec.c +++ b/drivers/misc/cros_ec.c @@ -1028,7 +1028,7 @@ int cros_ec_decode_ec_flash(struct udevice *dev, struct fdt_cros_ec *config) return -1; } - if (of_read_fmap_entry(flash_node, "flash", &config->flash)) { + if (ofnode_read_fmap_entry(flash_node, &config->flash)) { debug("Failed to decode flash node in chrome-ec\n"); return -1; } @@ -1050,7 +1050,7 @@ int cros_ec_decode_ec_flash(struct udevice *dev, struct fdt_cros_ec *config) return -1; } - if (of_read_fmap_entry(node, "reg", &config->region[region])) { + if (ofnode_read_fmap_entry(node, &config->region[region])) { debug("Failed to decode flash region in chrome-ec'\n"); return -1; } diff --git a/include/dm/of_extra.h b/include/dm/of_extra.h index 6f15296..e1540c1 100644 --- a/include/dm/of_extra.h +++ b/include/dm/of_extra.h @@ -34,12 +34,10 @@ struct fmap_entry { /** * Read a flash entry from the fdt * - * @param node Reference to node to read - * @param name Name of node being read + * @param node Reference to node to read * @param entry Place to put offset and size of this node * @return 0 if ok, -ve on error */ -int of_read_fmap_entry(ofnode node, const char *name, - struct fmap_entry *entry); +int ofnode_read_fmap_entry(ofnode node, struct fmap_entry *entry); #endif From 964cadc445f1437e63f1d2b4fffd233ac053c6e6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:18 -0600 Subject: [PATCH 10/40] dm: core: Add a function to decode a memory region Add a way to decode a memory region, including the memory type (sram or sdram) and its start address and size. Signed-off-by: Simon Glass --- drivers/core/of_extra.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ include/dm/of_extra.h | 45 +++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/drivers/core/of_extra.c b/drivers/core/of_extra.c index 3243caa..aa48917 100644 --- a/drivers/core/of_extra.c +++ b/drivers/core/of_extra.c @@ -34,3 +34,84 @@ int ofnode_read_fmap_entry(ofnode node, struct fmap_entry *entry) return 0; } + +int ofnode_decode_region(ofnode node, const char *prop_name, fdt_addr_t *basep, + fdt_size_t *sizep) +{ + const fdt_addr_t *cell; + int len; + + debug("%s: %s: %s\n", __func__, ofnode_get_name(node), prop_name); + cell = ofnode_get_property(node, prop_name, &len); + if (!cell || (len < sizeof(fdt_addr_t) * 2)) { + debug("cell=%p, len=%d\n", cell, len); + return -1; + } + + *basep = fdt_addr_to_cpu(*cell); + *sizep = fdt_size_to_cpu(cell[1]); + debug("%s: base=%08lx, size=%lx\n", __func__, (ulong)*basep, + (ulong)*sizep); + + return 0; +} + +int ofnode_decode_memory_region(ofnode config_node, const char *mem_type, + const char *suffix, fdt_addr_t *basep, + fdt_size_t *sizep) +{ + char prop_name[50]; + const char *mem; + fdt_size_t size, offset_size; + fdt_addr_t base, offset; + ofnode node; + + if (!ofnode_valid(config_node)) { + config_node = ofnode_path("/config"); + if (!ofnode_valid(config_node)) { + debug("%s: Cannot find /config node\n", __func__); + return -ENOENT; + } + } + if (!suffix) + suffix = ""; + + snprintf(prop_name, sizeof(prop_name), "%s-memory%s", mem_type, + suffix); + mem = ofnode_read_string(config_node, prop_name); + if (!mem) { + debug("%s: No memory type for '%s', using /memory\n", __func__, + prop_name); + mem = "/memory"; + } + + node = ofnode_path(mem); + if (!ofnode_valid(node)) { + debug("%s: Failed to find node '%s'\n", __func__, mem); + return -ENOENT; + } + + /* + * Not strictly correct - the memory may have multiple banks. We just + * use the first + */ + if (ofnode_decode_region(node, "reg", &base, &size)) { + debug("%s: Failed to decode memory region %s\n", __func__, + mem); + return -EINVAL; + } + + snprintf(prop_name, sizeof(prop_name), "%s-offset%s", mem_type, + suffix); + if (ofnode_decode_region(config_node, prop_name, &offset, + &offset_size)) { + debug("%s: Failed to decode memory region '%s'\n", __func__, + prop_name); + return -EINVAL; + } + + *basep = base + offset; + *sizep = offset_size; + + return 0; +} diff --git a/include/dm/of_extra.h b/include/dm/of_extra.h index e1540c1..97988b6 100644 --- a/include/dm/of_extra.h +++ b/include/dm/of_extra.h @@ -40,4 +40,49 @@ struct fmap_entry { */ int ofnode_read_fmap_entry(ofnode node, struct fmap_entry *entry); +/** + * ofnode_decode_region() - Decode a memory region from a node + * + * Look up a property in a node which contains a memory region address and + * size. Then return a pointer to this address. + * + * The property must hold one address with a length. This is only tested on + * 32-bit machines. + * + * @param node ofnode to examine + * @param prop_name name of property to find + * @param basep Returns base address of region + * @param size Returns size of region + * @return 0 if ok, -1 on error (property not found) + */ +int ofnode_decode_region(ofnode node, const char *prop_name, fdt_addr_t *basep, + fdt_size_t *sizep); + +/** + * ofnode_decode_memory_region()- Decode a named region within a memory bank + * + * This function handles selection of a memory region. The region is + * specified as an offset/size within a particular type of memory. + * + * The properties used are: + * + * -memory for the name of the memory bank + * -offset for the offset in that bank + * + * The property value must have an offset and a size. The function checks + * that the region is entirely within the memory bank.5 + * + * @param node ofnode containing the properties (-1 for /config) + * @param mem_type Type of memory to use, which is a name, such as + * "u-boot" or "kernel". + * @param suffix String to append to the memory/offset + * property names + * @param basep Returns base of region + * @param sizep Returns size of region + * @return 0 if OK, -ive on error + */ +int ofnode_decode_memory_region(ofnode config_node, const char *mem_type, + const char *suffix, fdt_addr_t *basep, + fdt_size_t *sizep); + #endif From 31e60ffa05b0952be3df8f584155567afdee50bd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 11 Jun 2018 13:07:19 -0600 Subject: [PATCH 11/40] dm: core: Add logging of some common errors Add additional logging so that common errors when finding a device by ofnode are easier to debug. Signed-off-by: Simon Glass --- drivers/core/uclass.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index 0085d3f..d609b17 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -308,6 +308,7 @@ int uclass_find_device_by_ofnode(enum uclass_id id, ofnode node, struct udevice *dev; int ret; + log(LOGC_DM, LOGL_DEBUG, "Looking for %s\n", ofnode_get_name(node)); *devp = NULL; if (!ofnode_valid(node)) return -ENODEV; @@ -316,13 +317,19 @@ int uclass_find_device_by_ofnode(enum uclass_id id, ofnode node, return ret; list_for_each_entry(dev, &uc->dev_head, uclass_node) { + log(LOGC_DM, LOGL_DEBUG_CONTENT, " - checking %s\n", + dev->name); if (ofnode_equal(dev_ofnode(dev), node)) { *devp = dev; - return 0; + goto done; } } + ret = -ENODEV; - return -ENODEV; +done: + log(LOGC_DM, LOGL_DEBUG, " - result for %s: %s (ret=%d)\n", + ofnode_get_name(node), *devp ? (*devp)->name : "(none)", ret); + return ret; } #if CONFIG_IS_ENABLED(OF_CONTROL) @@ -449,8 +456,11 @@ int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node, struct udevice *dev; int ret; + log(LOGC_DM, LOGL_DEBUG, "Looking for %s\n", ofnode_get_name(node)); *devp = NULL; ret = uclass_find_device_by_ofnode(id, node, &dev); + log(LOGC_DM, LOGL_DEBUG, " - result for %s: %s (ret=%d)\n", + ofnode_get_name(node), dev ? dev->name : "(none)", ret); return uclass_get_device_tail(dev, ret, devp); } From 16d836cd6fa0d22e24fa2340df08e6d6d80c5107 Mon Sep 17 00:00:00 2001 From: Tom Rini Date: Fri, 6 Jul 2018 10:27:14 -0600 Subject: [PATCH 12/40] binman: Switch to 'python-coverage' The most portable way to get access to coverage is to invoke it as 'python-coverage'. Cc: Simon Glass Signed-off-by: Tom Rini Signed-off-by: Simon Glass --- test/run | 3 +-- tools/binman/README | 3 +-- tools/binman/binman.py | 9 +++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/run b/test/run index eb1563d..0b9188e 100755 --- a/test/run +++ b/test/run @@ -26,8 +26,7 @@ PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test ./tools/dtoc/dtoc -t # 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-pip python-pytest -# $ sudo pip install coverage +# $ sudo apt-get install python-pytest python-coverage PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test \ ./tools/binman/binman -T diff --git a/tools/binman/README b/tools/binman/README index 22f21bc..f74e392 100644 --- a/tools/binman/README +++ b/tools/binman/README @@ -583,8 +583,7 @@ implementations target 100% test coverage. Run 'binman -T' to check this. To enable Python test coverage on Debian-type distributions (e.g. Ubuntu): - $ sudo apt-get install python-pip python-pytest - $ sudo pip install coverage + $ sudo apt-get install python-coverage python-pytest Advanced Features / Technical docs diff --git a/tools/binman/binman.py b/tools/binman/binman.py index 31b0453..944fd5d 100755 --- a/tools/binman/binman.py +++ b/tools/binman/binman.py @@ -81,24 +81,25 @@ def RunTests(debug, args): def RunTestCoverage(): """Run the tests and check that we get 100% coverage""" # This uses the build output from sandbox_spl to get _libfdt.so - cmd = ('PYTHONPATH=$PYTHONPATH:%s/sandbox_spl/tools coverage run ' + cmd = ('PYTHONPATH=$PYTHONPATH:%s/sandbox_spl/tools python-coverage run ' '--include "tools/binman/*.py" --omit "*test*,*binman.py" ' 'tools/binman/binman.py -t' % options.build_dir) os.system(cmd) - stdout = command.Output('coverage', 'report') + stdout = command.Output('python-coverage', 'report') lines = stdout.splitlines() test_set= set([os.path.basename(line.split()[0]) for line in lines if '/etype/' in line]) glob_list = glob.glob(os.path.join(our_path, 'etype/*.py')) - all_set = set([os.path.basename(item) for item in glob_list]) + all_set = set([os.path.splitext(os.path.basename(item))[0] + for item in glob_list if '_testing' not in item]) missing_list = all_set missing_list.difference_update(test_set) - missing_list.remove('_testing.py') coverage = lines[-1].split(' ')[-1] ok = True if missing_list: print 'Missing tests for %s' % (', '.join(missing_list)) + print stdout ok = False if coverage != '100%': print stdout From 1835cd2264ebd8814624b738d6e25ee772682cbe Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:15 -0600 Subject: [PATCH 13/40] binman: Make the operation of Entry__testing explicit This fake entry is used for testing. At present it only has one behaviour which is to return an invalid set of entry positions, to cause an error. The fake entry will need to be used for other things too. Allow the test .dts file to specify the behaviour of the fake entry, so we can control its behaviour easily. While we are here, drop the ReadContents() method, since this only applies to subclasses of Entry_blob, which Entry__testing is not. Signed-off-by: Simon Glass --- tools/binman/etype/_testing.py | 14 ++++++++++---- tools/binman/test/41_unknown_pos_size.dts | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/binman/etype/_testing.py b/tools/binman/etype/_testing.py index c376dd5..0b1eaef 100644 --- a/tools/binman/etype/_testing.py +++ b/tools/binman/etype/_testing.py @@ -10,16 +10,22 @@ import fdt_util import tools class Entry__testing(Entry): + """A fake entry used for testing + + Properties: + return_invalid_entry: Return an invalid entry from GetPositions() + """ def __init__(self, section, etype, node): Entry.__init__(self, section, etype, node) + self.return_invalid_entry = fdt_util.GetBool(self._node, + 'return-invalid-entry') def ObtainContents(self): self.data = 'a' self.contents_size = len(self.data) return True - def ReadContents(self): - return True - def GetPositions(self): - return {'invalid-entry': [1, 2]} + if self.return_invalid_entry : + return {'invalid-entry': [1, 2]} + return {} diff --git a/tools/binman/test/41_unknown_pos_size.dts b/tools/binman/test/41_unknown_pos_size.dts index a8e7d8a..94fe821 100644 --- a/tools/binman/test/41_unknown_pos_size.dts +++ b/tools/binman/test/41_unknown_pos_size.dts @@ -6,6 +6,7 @@ binman { _testing { + return-invalid-entry; }; }; }; From adc5701134f7041d7f767b672b66afb8b64ba5ce Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:16 -0600 Subject: [PATCH 14/40] binman: Tidy up variables in _RunMicrocodeTest() At present we call the three entries first, second and third. Rename them to reflect their contents instead, for clarity. Signed-off-by: Simon Glass --- tools/binman/ftest.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index eb8a079..80eadef 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -689,29 +689,40 @@ class TestFunctional(unittest.TestCase): self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)]) def _RunMicrocodeTest(self, dts_fname, nodtb_data): + """Handle running a test for insertion of microcode + + Args: + dts_fname: Name of test .dts file + nodtb_data: Data that we expect in the first section + + Returns: + Tuple: + Contents of first region (U-Boot or SPL) + Position and size components of microcode pointer, as inserted + in the above (two 4-byte words) + """ data = self._DoReadFile(dts_fname, True) # Now check the device tree has no microcode - second = data[len(nodtb_data):] + dtb_with_ucode = data[len(nodtb_data):] + fdt_len = self.GetFdtLen(dtb_with_ucode) + ucode_content = dtb_with_ucode[fdt_len:] + ucode_pos = len(nodtb_data) + fdt_len fname = tools.GetOutputFilename('test.dtb') with open(fname, 'wb') as fd: - fd.write(second) + fd.write(dtb_with_ucode) dtb = fdt.FdtScan(fname) ucode = dtb.GetNode('/microcode') self.assertTrue(ucode) for node in ucode.subnodes: self.assertFalse(node.props.get('data')) - fdt_len = self.GetFdtLen(second) - third = second[fdt_len:] - # Check that the microcode appears immediately after the Fdt # This matches the concatenation of the data properties in # the /microcode/update@xxx nodes in 34_x86_ucode.dts. ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000, 0x78235609) - self.assertEqual(ucode_data, third[:len(ucode_data)]) - ucode_pos = len(nodtb_data) + fdt_len + self.assertEqual(ucode_data, ucode_content[:len(ucode_data)]) # Check that the microcode pointer was inserted. It should match the # expected position and size From 736bb0aec80891891f3f86004e1afa7976585918 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:17 -0600 Subject: [PATCH 15/40] binman: Correct operation of ObtainContents() This method is supposed to return the contents of an entry. However at present there is no check that it actually does. Also some implementations do not return 'True' to indicate success, as required. Add a check for things working as expected, and correct the implementations. This requires some additional test cases to cover things which were missed originally. Add these at the same time. Signed-off-by: Simon Glass --- tools/binman/bsection.py | 4 ++ tools/binman/etype/_testing.py | 5 ++ tools/binman/etype/section.py | 2 +- tools/binman/etype/u_boot_spl_bss_pad.py | 1 + tools/binman/etype/u_boot_ucode.py | 9 +++- tools/binman/ftest.py | 57 +++++++++++++++++----- tools/binman/test/57_unknown_contents.dts | 14 ++++++ tools/binman/test/58_x86_ucode_spl_needs_retry.dts | 36 ++++++++++++++ 8 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 tools/binman/test/57_unknown_contents.dts create mode 100644 tools/binman/test/58_x86_ucode_spl_needs_retry.dts diff --git a/tools/binman/bsection.py b/tools/binman/bsection.py index 3f30f6e..06a6711 100644 --- a/tools/binman/bsection.py +++ b/tools/binman/bsection.py @@ -162,6 +162,10 @@ class Section(object): 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 _SetEntryPosSize(self, name, pos, size): """Set the position and size of an entry diff --git a/tools/binman/etype/_testing.py b/tools/binman/etype/_testing.py index 0b1eaef..c075c3f 100644 --- a/tools/binman/etype/_testing.py +++ b/tools/binman/etype/_testing.py @@ -9,6 +9,7 @@ from entry import Entry import fdt_util import tools + class Entry__testing(Entry): """A fake entry used for testing @@ -19,8 +20,12 @@ class Entry__testing(Entry): Entry.__init__(self, section, etype, node) self.return_invalid_entry = fdt_util.GetBool(self._node, 'return-invalid-entry') + self.return_unknown_contents = fdt_util.GetBool(self._node, + 'return-unknown-contents') def ObtainContents(self): + if self.return_unknown_contents: + return False self.data = 'a' self.contents_size = len(self.data) return True diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 139fcad..36b31a8 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -18,7 +18,7 @@ class Entry_section(Entry): self._section = bsection.Section(node.name, node) def ObtainContents(self): - self._section.GetEntryContents() + return self._section.GetEntryContents() def GetData(self): return self._section.GetData() diff --git a/tools/binman/etype/u_boot_spl_bss_pad.py b/tools/binman/etype/u_boot_spl_bss_pad.py index 3d2dea2..6c39795 100644 --- a/tools/binman/etype/u_boot_spl_bss_pad.py +++ b/tools/binman/etype/u_boot_spl_bss_pad.py @@ -24,3 +24,4 @@ class Entry_u_boot_spl_bss_pad(Entry_blob): self.Raise('Expected __bss_size symbol in spl/u-boot-spl') self.data = chr(0) * bss_size self.contents_size = bss_size + return True diff --git a/tools/binman/etype/u_boot_ucode.py b/tools/binman/etype/u_boot_ucode.py index 3a0cff7..8cae7de 100644 --- a/tools/binman/etype/u_boot_ucode.py +++ b/tools/binman/etype/u_boot_ucode.py @@ -64,9 +64,14 @@ class Entry_u_boot_ucode(Entry_blob): self.data = '' return True - # Get the microcode from the device tree entry + # Get the microcode from the device tree entry. If it is not available + # yet, return False so we will be called later. If the section simply + # doesn't exist, then we may as well return True, since we are going to + # get an error anyway. fdt_entry = self.section.FindEntryType('u-boot-dtb-with-ucode') - if not fdt_entry or not fdt_entry.ucode_data: + if not fdt_entry: + return True + if not fdt_entry.ucode_data: return False if not fdt_entry.collate: diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 80eadef..ca9d158 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -688,12 +688,14 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile('33_x86-start16.dts') self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)]) - def _RunMicrocodeTest(self, dts_fname, nodtb_data): + def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False): """Handle running a test for insertion of microcode Args: dts_fname: Name of test .dts file nodtb_data: Data that we expect in the first section + ucode_second: True if the microsecond entry is second instead of + third Returns: Tuple: @@ -704,10 +706,16 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile(dts_fname, True) # Now check the device tree has no microcode - dtb_with_ucode = data[len(nodtb_data):] - fdt_len = self.GetFdtLen(dtb_with_ucode) - ucode_content = dtb_with_ucode[fdt_len:] - ucode_pos = len(nodtb_data) + fdt_len + if ucode_second: + ucode_content = data[len(nodtb_data):] + ucode_pos = len(nodtb_data) + dtb_with_ucode = ucode_content[16:] + fdt_len = self.GetFdtLen(dtb_with_ucode) + else: + dtb_with_ucode = data[len(nodtb_data):] + fdt_len = self.GetFdtLen(dtb_with_ucode) + ucode_content = dtb_with_ucode[fdt_len:] + ucode_pos = len(nodtb_data) + fdt_len fname = tools.GetOutputFilename('test.dtb') with open(fname, 'wb') as fd: fd.write(dtb_with_ucode) @@ -728,8 +736,8 @@ class TestFunctional(unittest.TestCase): # expected position and size pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos, len(ucode_data)) - first = data[:len(nodtb_data)] - return first, pos_and_size + u_boot = data[:len(nodtb_data)] + return u_boot, pos_and_size def testPackUbootMicrocode(self): """Test that x86 microcode can be handled correctly @@ -892,23 +900,42 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile('48_x86-start16-spl.dts') self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)]) - def testPackUbootSplMicrocode(self): - """Test that x86 microcode can be handled correctly in SPL + def _PackUbootSplMicrocode(self, dts, ucode_second=False): + """Helper function for microcode tests We expect to see the following in the image, in order: u-boot-spl-nodtb.bin with a microcode pointer inserted at the correct place u-boot.dtb with the microcode removed the microcode + + Args: + dts: Device tree file to use for test + ucode_second: True if the microsecond entry is second instead of + third """ # ELF file with a '_dt_ucode_base_size' symbol with open(self.TestFile('u_boot_ucode_ptr')) as fd: TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read()) - first, pos_and_size = self._RunMicrocodeTest('49_x86_ucode_spl.dts', - U_BOOT_SPL_NODTB_DATA) + first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA, + ucode_second=ucode_second) self.assertEqual('splnodtb with microc' + pos_and_size + 'ter somewhere in here', first) + def testPackUbootSplMicrocode(self): + """Test that x86 microcode can be handled correctly in SPL""" + self._PackUbootSplMicrocode('49_x86_ucode_spl.dts') + + def testPackUbootSplMicrocodeReorder(self): + """Test that order doesn't matter for microcode entries + + This is the same as testPackUbootSplMicrocode but when we process the + u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode + entry, so we reply on binman to try later. + """ + self._PackUbootSplMicrocode('58_x86_ucode_spl_needs_retry.dts', + ucode_second=True) + def testPackMrc(self): """Test that an image with an MRC binary can be created""" data = self._DoReadFile('50_intel_mrc.dts') @@ -971,5 +998,13 @@ class TestFunctional(unittest.TestCase): 00000000 00000004 rw-u-boot ''', map_data) + def testUnknownContents(self): + """Test that obtaining the contents works as expected""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('57_unknown_contents.dts', True) + self.assertIn("Section '/binman': Internal error: Could not complete " + "processing of contents: remaining [<_testing.Entry__testing ", + str(e.exception)) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/57_unknown_contents.dts b/tools/binman/test/57_unknown_contents.dts new file mode 100644 index 0000000..6ea98d7 --- /dev/null +++ b/tools/binman/test/57_unknown_contents.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + _testing { + return-unknown-contents; + }; + }; +}; diff --git a/tools/binman/test/58_x86_ucode_spl_needs_retry.dts b/tools/binman/test/58_x86_ucode_spl_needs_retry.dts new file mode 100644 index 0000000..e2cb80c --- /dev/null +++ b/tools/binman/test/58_x86_ucode_spl_needs_retry.dts @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + sort-by-pos; + end-at-4gb; + size = <0x200>; + u-boot-spl-with-ucode-ptr { + }; + + /* + * Microcode goes before the DTB which contains it, so binman + * will need to obtain the contents of the next section before + * obtaining the contents of this one. + */ + u-boot-ucode { + }; + + u-boot-dtb-with-ucode { + }; + }; + + microcode { + update@0 { + data = <0x12345678 0x12345679>; + }; + update@1 { + data = <0xabcd0000 0x78235609>; + }; + }; +}; From 2cd01285b53f376e439e4cbdbce808ca8231ef84 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:18 -0600 Subject: [PATCH 16/40] binman: Tidy up execution of tests Move all the test execution into the same mechanism so that we can request a particular test (from any suite) by passing it as an argument to 'binman -t'. Signed-off-by: Simon Glass --- tools/binman/binman.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/binman/binman.py b/tools/binman/binman.py index 944fd5d..74862b1 100755 --- a/tools/binman/binman.py +++ b/tools/binman/binman.py @@ -54,14 +54,12 @@ def RunTests(debug, args): # Run the entry tests first ,since these need to be the first to import the # 'entry' module. - suite = unittest.TestLoader().loadTestsFromTestCase(entry_test.TestEntry) - suite.run(result) test_name = args and args[0] or None - for module in (ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf, - image_test.TestImage): + for module in (entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt, + elf_test.TestElf, image_test.TestImage): if test_name: try: - suite = unittest.TestLoader().loadTestsFromName(args[0], module) + suite = unittest.TestLoader().loadTestsFromName(test_name, module) except AttributeError: continue else: From 5c890238c480a96d4d0b06c92199e21867170c31 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:19 -0600 Subject: [PATCH 17/40] binman: Tidy up setting of entry contents At present the contents of an entry are set in subclasses simply by assigning to the data and content_size properties. Add some methods to do this, so that we have more control. In particular, add a method to set the contents without changing its size, so we can validate that case. Add a test case for trying to change the size when this is not allowed. Signed-off-by: Simon Glass --- tools/binman/entry.py | 28 ++++++++++++++++++++++++++++ tools/binman/etype/_testing.py | 8 ++++++++ tools/binman/etype/blob.py | 3 +-- tools/binman/etype/u_boot_spl_bss_pad.py | 3 +-- tools/binman/etype/u_boot_with_ucode_ptr.py | 4 ++-- tools/binman/ftest.py | 8 ++++++++ tools/binman/test/59_change_size.dts | 14 ++++++++++++++ 7 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 tools/binman/test/59_change_size.dts diff --git a/tools/binman/entry.py b/tools/binman/entry.py index e4d688c..303c992 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -55,6 +55,7 @@ class Entry(object): self.name = node and (name_prefix + node.name) or 'none' self.pos = None self.size = None + self.data = '' self.contents_size = 0 self.align = None self.align_size = None @@ -138,6 +139,33 @@ class Entry(object): if prefix: self.name = prefix + self.name + def SetContents(self, data): + """Set the contents of an entry + + This sets both the data and content_size properties + + Args: + data: Data to set to the contents (string) + """ + self.data = data + self.contents_size = len(self.data) + + def ProcessContentsUpdate(self, data): + """Update the contens of an entry, after the size is fixed + + This checks that the new data is the same size as the old. + + Args: + data: Data to set to the contents (string) + + Raises: + ValueError if the new data size is not the same as the old + """ + if len(data) != self.contents_size: + self.Raise('Cannot update entry size from %d to %d' % + (len(data), self.contents_size)) + self.SetContents(data) + def ObtainContents(self): """Figure out the contents of an entry. diff --git a/tools/binman/etype/_testing.py b/tools/binman/etype/_testing.py index c075c3f..04bdc6c 100644 --- a/tools/binman/etype/_testing.py +++ b/tools/binman/etype/_testing.py @@ -22,6 +22,8 @@ class Entry__testing(Entry): 'return-invalid-entry') self.return_unknown_contents = fdt_util.GetBool(self._node, 'return-unknown-contents') + self.bad_update_contents = fdt_util.GetBool(self._node, + 'bad-update-contents') def ObtainContents(self): if self.return_unknown_contents: @@ -34,3 +36,9 @@ class Entry__testing(Entry): if self.return_invalid_entry : return {'invalid-entry': [1, 2]} return {} + + def ProcessContents(self): + if self.bad_update_contents: + # Request to update the conents with something larger, to cause a + # failure. + self.ProcessContentsUpdate('aa') diff --git a/tools/binman/etype/blob.py b/tools/binman/etype/blob.py index 16b1e5f..28e6651 100644 --- a/tools/binman/etype/blob.py +++ b/tools/binman/etype/blob.py @@ -28,8 +28,7 @@ class Entry_blob(Entry): # 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. - self.data = fd.read() - self.contents_size = len(self.data) + self.SetContents(fd.read()) return True def GetDefaultFilename(self): diff --git a/tools/binman/etype/u_boot_spl_bss_pad.py b/tools/binman/etype/u_boot_spl_bss_pad.py index 6c39795..65f631d 100644 --- a/tools/binman/etype/u_boot_spl_bss_pad.py +++ b/tools/binman/etype/u_boot_spl_bss_pad.py @@ -22,6 +22,5 @@ class Entry_u_boot_spl_bss_pad(Entry_blob): bss_size = elf.GetSymbolAddress(fname, '__bss_size') if not bss_size: self.Raise('Expected __bss_size symbol in spl/u-boot-spl') - self.data = chr(0) * bss_size - self.contents_size = bss_size + self.SetContents(chr(0) * bss_size) return True diff --git a/tools/binman/etype/u_boot_with_ucode_ptr.py b/tools/binman/etype/u_boot_with_ucode_ptr.py index 41c2ded..86945f3 100644 --- a/tools/binman/etype/u_boot_with_ucode_ptr.py +++ b/tools/binman/etype/u_boot_with_ucode_ptr.py @@ -81,5 +81,5 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob): # Write the microcode position and size into the entry pos_and_size = struct.pack('<2L', pos, size) self.target_pos -= self.pos - self.data = (self.data[:self.target_pos] + pos_and_size + - self.data[self.target_pos + 8:]) + self.ProcessContentsUpdate(self.data[:self.target_pos] + pos_and_size + + self.data[self.target_pos + 8:]) diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index ca9d158..af3b4dc 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -1006,5 +1006,13 @@ class TestFunctional(unittest.TestCase): "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('59_change_size.dts', True) + self.assertIn("Node '/binman/_testing': Cannot update entry size from " + '2 to 1', str(e.exception)) + + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/59_change_size.dts b/tools/binman/test/59_change_size.dts new file mode 100644 index 0000000..1a69026 --- /dev/null +++ b/tools/binman/test/59_change_size.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + _testing { + bad-update-contents; + }; + }; +}; From 3def0cf238e0df9736a4ce8fb54c1eb561a56ddd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:20 -0600 Subject: [PATCH 18/40] libfdt: Bring in proposed pylibfdt changes This provides various patches sent to the devicetree-compiler mailing list to enhance the Python bindings. A final version of this patch may be created once upstreaming is complete, but if it takes too long, this can act as a placeholder. New pylibfdt features: - Support for most remaining, relevant libfdt functions - Support for sequential-write functions Changes are applied to existing U-Boot tools as needed. Signed-off-by: Simon Glass --- scripts/dtc/libfdt/libfdt.h | 3 + scripts/dtc/pylibfdt/libfdt.i_shipped | 705 +++++++++++++++++++++++++++++++--- tools/dtoc/dtoc.py | 20 +- tools/dtoc/fdt.py | 3 +- tools/dtoc/test_dtoc.py | 3 +- 5 files changed, 663 insertions(+), 71 deletions(-) diff --git a/scripts/dtc/libfdt/libfdt.h b/scripts/dtc/libfdt/libfdt.h index 1e27780..fd73688 100644 --- a/scripts/dtc/libfdt/libfdt.h +++ b/scripts/dtc/libfdt/libfdt.h @@ -1313,10 +1313,13 @@ static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) fdt64_t tmp = cpu_to_fdt64(val); return fdt_property(fdt, name, &tmp, sizeof(tmp)); } + +#ifndef SWIG /* Not available in Python */ static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) { return fdt_property_u32(fdt, name, val); } +#endif /** * fdt_property_placeholder - add a new property and return a ptr to its value diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 2c1c987..6774b93 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -12,6 +12,17 @@ %{ #define SWIG_FILE_WITH_INIT #include "libfdt.h" + +/* + * We rename this function here to avoid problems with swig, since we also have + * a struct called fdt_property. That struct causes swig to create a class in + * libfdt.py called fdt_property(), which confuses things. + */ +static int _fdt_property(void *fdt, const char *name, const char *val, int len) +{ + return fdt_property(fdt, name, val, len); +} + %} %pythoncode %{ @@ -108,6 +119,7 @@ def check_err_null(val, quiet=()): raise FdtException(val) return val + class Fdt: """Device tree class, supporting all operations @@ -129,6 +141,163 @@ class Fdt: self._fdt = bytearray(data) check_err(fdt_check_header(self._fdt)); + def as_bytearray(self): + """Get the device tree contents as a bytearray + + This can be passed directly to libfdt functions that access a + const void * for the device tree. + + Returns: + bytearray containing the device tree + """ + return bytearray(self._fdt) + + def next_node(self, nodeoffset, depth, quiet=()): + """Find the next subnode + + Args: + nodeoffset: Node offset of previous node + depth: On input, the depth of the node at nodeoffset. On output, the + depth of the returned node + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + The offset of the next node, if any + + Raises: + FdtException if no more nodes found or other error occurs + """ + return check_err(fdt_next_node(self._fdt, nodeoffset, depth), quiet) + + def first_subnode(self, nodeoffset, quiet=()): + """Find the first subnode of a parent node + + Args: + nodeoffset: Node offset of parent node + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + The offset of the first subnode, if any + + Raises: + FdtException if no subnodes found or other error occurs + """ + return check_err(fdt_first_subnode(self._fdt, nodeoffset), quiet) + + def next_subnode(self, nodeoffset, quiet=()): + """Find the next subnode + + Args: + nodeoffset: Node offset of previous subnode + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + The offset of the next subnode, if any + + Raises: + FdtException if no more subnodes found or other error occurs + """ + return check_err(fdt_next_subnode(self._fdt, nodeoffset), quiet) + + def magic(self): + """Return the magic word from the header + + Returns: + Magic word + """ + return fdt_magic(self._fdt) & 0xffffffff + + def totalsize(self): + """Return the total size of the device tree + + Returns: + Total tree size in bytes + """ + return check_err(fdt_totalsize(self._fdt)) + + def off_dt_struct(self): + """Return the start of the device-tree struct area + + Returns: + Start offset of struct area + """ + return check_err(fdt_off_dt_struct(self._fdt)) + + def off_dt_strings(self): + """Return the start of the device-tree string area + + Returns: + Start offset of string area + """ + return check_err(fdt_off_dt_strings(self._fdt)) + + def off_mem_rsvmap(self): + """Return the start of the memory reserve map + + Returns: + Start offset of memory reserve map + """ + return check_err(fdt_off_mem_rsvmap(self._fdt)) + + def version(self): + """Return the version of the device tree + + Returns: + Version number of the device tree + """ + return check_err(fdt_version(self._fdt)) + + def last_comp_version(self): + """Return the last compatible version of the device tree + + Returns: + Last compatible version number of the device tree + """ + return check_err(fdt_last_comp_version(self._fdt)) + + def boot_cpuid_phys(self): + """Return the physical boot CPU ID + + Returns: + Physical boot CPU ID + """ + return check_err(fdt_boot_cpuid_phys(self._fdt)) + + def size_dt_strings(self): + """Return the start of the device-tree string area + + Returns: + Start offset of string area + """ + return check_err(fdt_size_dt_strings(self._fdt)) + + def size_dt_struct(self): + """Return the start of the device-tree struct area + + Returns: + Start offset of struct area + """ + return check_err(fdt_size_dt_struct(self._fdt)) + + def num_mem_rsv(self, quiet=()): + """Return the number of memory reserve-map records + + Returns: + Number of memory reserve-map records + """ + return check_err(fdt_num_mem_rsv(self._fdt), quiet) + + def get_mem_rsv(self, index, quiet=()): + """Return the indexed memory reserve-map record + + Args: + index: Record to return (0=first) + + Returns: + Number of memory reserve-map records + """ + return check_err(fdt_get_mem_rsv(self._fdt, index), quiet) + def subnode_offset(self, parentoffset, name, quiet=()): """Get the offset of a named subnode @@ -161,6 +330,20 @@ class Fdt: """ return check_err(fdt_path_offset(self._fdt, path), quiet) + def get_name(self, nodeoffset): + """Get the name of a node + + Args: + nodeoffset: Offset of node to check + + Returns: + Node name + + Raises: + FdtException on error (e.g. nodeoffset is invalid) + """ + return check_err_null(fdt_get_name(self._fdt, nodeoffset))[0] + def first_property_offset(self, nodeoffset, quiet=()): """Get the offset of the first property in a node offset @@ -195,20 +378,6 @@ class Fdt: return check_err(fdt_next_property_offset(self._fdt, prop_offset), quiet) - def get_name(self, nodeoffset): - """Get the name of a node - - Args: - nodeoffset: Offset of node to check - - Returns: - Node name - - Raises: - FdtException on error (e.g. nodeoffset is invalid) - """ - return check_err_null(fdt_get_name(self._fdt, nodeoffset))[0] - def get_property_by_offset(self, prop_offset, quiet=()): """Obtains a property that can be examined @@ -229,51 +398,38 @@ class Fdt: return pdata return Property(pdata[0], pdata[1]) - def first_subnode(self, nodeoffset, quiet=()): - """Find the first subnode of a parent node + @staticmethod + def create_empty_tree(size, quiet=()): + """Create an empty device tree ready for use Args: - nodeoffset: Node offset of parent node - quiet: Errors to ignore (empty to raise on all errors) + size: Size of device tree in bytes Returns: - The offset of the first subnode, if any - - Raises: - FdtException if no subnode found or other error occurs + Fdt object containing the device tree """ - return check_err(fdt_first_subnode(self._fdt, nodeoffset), quiet) - - def next_subnode(self, nodeoffset, quiet=()): - """Find the next subnode + data = bytearray(size) + err = check_err(fdt_create_empty_tree(data, size), quiet) + if err: + return err + return Fdt(data) - Args: - nodeoffset: Node offset of previous subnode - quiet: Errors to ignore (empty to raise on all errors) + def open_into(self, size, quiet=()): + """Move the device tree into a larger or smaller space - Returns: - The offset of the next subnode, if any + This creates a new device tree of size @size and moves the existing + device tree contents over to that. It can be used to create more space + in a device tree. - Raises: - FdtException if no more subnode found or other error occurs - """ - return check_err(fdt_next_subnode(self._fdt, nodeoffset), quiet) - - def totalsize(self): - """Return the total size of the device tree - - Returns: - Total tree size in bytes - """ - return check_err(fdt_totalsize(self._fdt)) - - def off_dt_struct(self): - """Return the start of the device tree struct area - - Returns: - Start offset of struct area + Args: + size: Required new size of device tree in bytes """ - return check_err(fdt_off_dt_struct(self._fdt)) + fdt = bytearray(size) + fdt[:len(self._fdt)] = self._fdt + err = check_err(fdt_open_into(self._fdt, fdt, size), quiet) + if err: + return err + self._fdt = fdt def pack(self, quiet=()): """Pack the device tree to remove unused space @@ -288,20 +444,28 @@ class Fdt: """ return check_err(fdt_pack(self._fdt), quiet) - def delprop(self, nodeoffset, prop_name): - """Delete a property from a node + def getprop(self, nodeoffset, prop_name, quiet=()): + """Get a property from a node Args: - nodeoffset: Node offset containing property to delete - prop_name: Name of property to delete + nodeoffset: Node offset containing property to get + prop_name: Name of property to get + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Value of property as a string, or -ve error number Raises: - FdtError if the property does not exist, or another error occurs + FdtError if any error occurs (e.g. the property is not found) """ - return check_err(fdt_delprop(self._fdt, nodeoffset, prop_name)) + pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), + quiet) + if isinstance(pdata, (int)): + return pdata + return str(pdata[0]) - def getprop(self, nodeoffset, prop_name, quiet=()): - """Get a property from a node + def getprop_obj(self, nodeoffset, prop_name, quiet=()): + """Get a property from a node as a Property object Args: nodeoffset: Node offset containing property to get @@ -309,7 +473,7 @@ class Fdt: quiet: Errors to ignore (empty to raise on all errors) Returns: - Value of property as a bytearray, or -ve error number + Property object, or None if not found Raises: FdtError if any error occurs (e.g. the property is not found) @@ -317,8 +481,8 @@ class Fdt: pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), quiet) if isinstance(pdata, (int)): - return pdata - return bytearray(pdata[0]) + return None + return Property(prop_name, bytearray(pdata[0])) def get_phandle(self, nodeoffset): """Get the phandle of a node @@ -347,6 +511,108 @@ class Fdt: """ return check_err(fdt_parent_offset(self._fdt, nodeoffset), quiet) + def set_name(self, nodeoffset, name, quiet=()): + """Set the name of a node + + Args: + nodeoffset: Node offset of node to update + name: New node name + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + return check_err(fdt_set_name(self._fdt, nodeoffset, name), quiet) + + def setprop(self, nodeoffset, prop_name, val, quiet=()): + """Set the value of a property + + Args: + nodeoffset: Node offset containing the property to create/update + prop_name: Name of property + val: Value to write (string or bytearray) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, val, + len(val)), quiet) + + def setprop_u32(self, nodeoffset, prop_name, val, quiet=()): + """Set the value of a property + + Args: + nodeoffset: Node offset containing the property to create/update + prop_name: Name of property + val: Value to write (integer) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + return check_err(fdt_setprop_u32(self._fdt, nodeoffset, prop_name, val), + quiet) + + def setprop_u64(self, nodeoffset, prop_name, val, quiet=()): + """Set the value of a property + + Args: + nodeoffset: Node offset containing the property to create/update + prop_name: Name of property + val: Value to write (integer) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + return check_err(fdt_setprop_u64(self._fdt, nodeoffset, prop_name, val), + quiet) + + def setprop_str(self, nodeoffset, prop_name, val, quiet=()): + """Set the string value of a property + + The property is set to the string, with a nul terminator added + + Args: + nodeoffset: Node offset containing the property to create/update + prop_name: Name of property + val: Value to write (string without nul terminator) + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Error code, or 0 if OK + + Raises: + FdtException if no parent found or other error occurs + """ + val += '\0' + return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, + val, len(val)), quiet) + + def delprop(self, nodeoffset, prop_name): + """Delete a property from a node + + Args: + nodeoffset: Node offset containing property to delete + prop_name: Name of property to delete + + Raises: + FdtError if the property does not exist, or another error occurs + """ + return check_err(fdt_delprop(self._fdt, nodeoffset, prop_name)) + def node_offset_by_phandle(self, phandle, quiet=()): """Get the offset of a node with the given phandle @@ -362,7 +628,8 @@ class Fdt: """ return check_err(fdt_node_offset_by_phandle(self._fdt, phandle), quiet) -class Property: + +class Property(bytearray): """Holds a device tree property name and value. This holds a copy of a property taken from the device tree. It does not @@ -371,11 +638,274 @@ class Property: Properties: name: Property name - value: Proper value as a bytearray + value: Property value as a bytearray """ def __init__(self, name, value): + bytearray.__init__(self, value) self.name = name - self.value = value + + def as_cell(self, fmt): + return struct.unpack('>' + fmt, self)[0] + + def as_uint32(self): + return self.as_cell('L') + + def as_int32(self): + return self.as_cell('l') + + def as_uint64(self): + return self.as_cell('Q') + + def as_int64(self): + return self.as_cell('q') + + def as_str(self): + return self[:-1] + + +class FdtSw(object): + """Software interface to create a device tree from scratch + + The methods in this class work by adding to an existing 'partial' device + tree buffer of a fixed size created by instantiating this class. When the + tree is complete, call finish() to complete the device tree so that it can + be used. + + Similarly with nodes, a new node is started with begin_node() and finished + with end_node(). + + The context manager functions can be used to make this a bit easier: + + # First create the device tree with a node and property: + with FdtSw(small_size) as sw: + with sw.AddNode('node'): + sw.property_u32('reg', 2) + fdt = sw.AsFdt() + + # Now we can use it as a real device tree + fdt.setprop_u32(0, 'reg', 3) + """ + def __init__(self, size, quiet=()): + fdtrw = bytearray(size) + err = check_err(fdt_create(fdtrw, size)) + if err: + return err + self._fdtrw = fdtrw + + def __enter__(self): + """Contact manager to use to create a device tree via software""" + return self + + def __exit__(self, type, value, traceback): + check_err(fdt_finish(self._fdtrw)) + + def AsFdt(self): + """Convert a FdtSw into an Fdt so it can be accessed as normal + + Note that finish() must be called before this function will work. If + you are using the context manager (see 'with' code in the FdtSw class + comment) then this will happen automatically. + + Returns: + Fdt object allowing access to the newly created device tree + """ + return Fdt(self._fdtrw) + + def resize(self, size, quiet=()): + """Resize the buffer to accommodate a larger tree + + Args: + size: New size of tree + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + fdt = bytearray(size) + fdt[:len(self._fdtrw)] = self._fdtrw + err = check_err(fdt_resize(self._fdtrw, fdt, size), quiet) + if err: + return err + self._fdtrw = fdt + + def add_reservemap_entry(self, addr, size, quiet=()): + """Add a new memory reserve map entry + + Once finished adding, you must call finish_reservemap(). + + Args: + addr: 64-bit start address + size: 64-bit size + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_add_reservemap_entry(self._fdtrw, addr, size), + quiet) + + def finish_reservemap(self, quiet=()): + """Indicate that there are no more reserve map entries to add + + Args: + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_finish_reservemap(self._fdtrw), quiet) + + def begin_node(self, name, quiet=()): + """Begin a new node + + Use this before adding properties to the node. Then call end_node() to + finish it. You can also use the context manager as shown in the FdtSw + class comment. + + Args: + name: Name of node to begin + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_begin_node(self._fdtrw, name), quiet) + + def property_string(self, name, string, quiet=()): + """Add a property with a string value + + The string will be nul-terminated when written to the device tree + + Args: + name: Name of property to add + string: String value of property + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_property_string(self._fdtrw, name, string), quiet) + + def property_u32(self, name, val, quiet=()): + """Add a property with a 32-bit value + + Write a single-cell value to the device tree + + Args: + name: Name of property to add + val: Value of property + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_property_u32(self._fdtrw, name, val), quiet) + + def property_u64(self, name, val, quiet=()): + """Add a property with a 64-bit value + + Write a double-cell value to the device tree in big-endian format + + Args: + name: Name of property to add + val: Value of property + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_property_u64(self._fdtrw, name, val), quiet) + + def property_cell(self, name, val, quiet=()): + """Add a property with a single-cell value + + Write a single-cell value to the device tree + + Args: + name: Name of property to add + val: Value of property + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_property_cell(self._fdtrw, name, val), quiet) + + def property(self, name, val, quiet=()): + """Add a property + + Write a new property with the given value to the device tree. The value + is taken as is and is not nul-terminated + + Args: + name: Name of property to add + val: Value of property + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(_fdt_property(self._fdtrw, name, val, len(val)), quiet) + + def end_node(self, quiet=()): + """End a node + + Use this after adding properties to a node to close it off. You can also + use the context manager as shown in the FdtSw class comment. + + Args: + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_end_node(self._fdtrw), quiet) + + def finish(self, quiet=()): + """Finish writing the device tree + + This closes off the device tree ready for use + + Args: + quiet: Errors to ignore (empty to raise on all errors) + + Raises: + FdtException if no node found or other error occurs + """ + return check_err(fdt_finish(self._fdtrw), quiet) + + def AddNode(self, name): + """Create a new context for adding a node + + When used in a 'with' clause this starts a new node and finishes it + afterward. + + Args: + name: Name of node to add + """ + return NodeAdder(self._fdtrw, name) + + +class NodeAdder(): + """Class to provide a node context + + This allows you to add nodes in a more natural way: + + with fdtsw.AddNode('name'): + fdtsw.property_string('test', 'value') + + The node is automatically completed with a call to end_node() when the + context exits. + """ + def __init__(self, fdt, name): + self._fdt = fdt + self._name = name + + def __enter__(self): + check_err(fdt_begin_node(self._fdt, self._name)) + + def __exit__(self, type, value, traceback): + check_err(fdt_end_node(self._fdt)) %} %rename(fdt_property) fdt_property_func; @@ -408,6 +938,7 @@ typedef int fdt32_t; fdt = fdt; /* avoid unused variable warning */ } +/* typemap used for fdt_get_property_by_offset() */ %typemap(out) (struct fdt_property *) { PyObject *buff; @@ -430,6 +961,44 @@ typedef int fdt32_t; $result = Py_BuildValue("s#", $1, *arg4); } +/* typemap used for fdt_setprop() */ +%typemap(in) (const void *val) { + $1 = PyString_AsString($input); /* char *str */ +} + +/* typemap used for fdt_add_reservemap_entry() */ +%typemap(in) uint64_t { + $1 = PyLong_AsUnsignedLong($input); +} + +/* typemaps used for fdt_next_node() */ +%typemap(in, numinputs=1) int *depth (int depth) { + depth = (int) PyInt_AsLong($input); + $1 = &depth; +} + +%typemap(argout) int *depth { + PyObject *val = Py_BuildValue("i", *arg$argnum); + resultobj = SWIG_Python_AppendOutput(resultobj, val); +} + +%apply int *depth { int *depth }; + +/* typemaps for fdt_get_mem_rsv */ +%typemap(in, numinputs=0) uint64_t * (uint64_t temp) { + $1 = &temp; +} + +%typemap(argout) uint64_t * { + PyObject *val = PyLong_FromUnsignedLong(*arg$argnum); + if (!result) { + if (PyTuple_GET_SIZE(resultobj) == 0) + resultobj = val; + else + resultobj = SWIG_Python_AppendOutput(resultobj, val); + } +} + /* We have both struct fdt_property and a function fdt_property() */ %warnfilter(302) fdt_property; @@ -444,5 +1013,13 @@ int fdt_last_comp_version(const void *fdt); int fdt_boot_cpuid_phys(const void *fdt); int fdt_size_dt_strings(const void *fdt); int fdt_size_dt_struct(const void *fdt); +int fdt_property_string(void *fdt, const char *name, const char *val); +int fdt_property_cell(void *fdt, const char *name, uint32_t val); + +/* + * This function has a stub since the name fdt_property is used for both a + * function and a struct, which confuses SWIG. + */ +int _fdt_property(void *fdt, const char *name, const char *val, int len); %include <../libfdt/libfdt.h> diff --git a/tools/dtoc/dtoc.py b/tools/dtoc/dtoc.py index 008c0f4..c891b06 100755 --- a/tools/dtoc/dtoc.py +++ b/tools/dtoc/dtoc.py @@ -36,14 +36,26 @@ sys.path.append(os.path.join(our_path, '../patman')) import dtb_platdata -def run_tests(): - """Run all the test we have for dtoc""" +def run_tests(args): + """Run all the test we have for dtoc + + Args: + args: List of positional args provided to binman. This can hold a test + name to execute (as in 'binman -t testSections', for example) + """ import test_dtoc result = unittest.TestResult() sys.argv = [sys.argv[0]] + test_name = args and args[0] or None for module in (test_dtoc.TestDtoc,): - suite = unittest.TestLoader().loadTestsFromTestCase(module) + if test_name: + try: + suite = unittest.TestLoader().loadTestsFromName(test_name, module) + except AttributeError: + continue + else: + suite = unittest.TestLoader().loadTestsFromTestCase(module) suite.run(result) print result @@ -68,7 +80,7 @@ parser.add_option('-t', '--test', action='store_true', dest='test', # Run our meagre tests if options.test: - run_tests() + run_tests(args) else: dtb_platdata.run_steps(args, options.dtb_file, options.include_disabled, diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index 7fab0cd..d08b0b5 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -234,7 +234,6 @@ class Node: be updated. """ if self._offset != my_offset: - #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset) self._offset = my_offset offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset) for subnode in self.subnodes: @@ -359,7 +358,7 @@ class Fdt: poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset) while poffset >= 0: p = self._fdt_obj.get_property_by_offset(poffset) - prop = Prop(node, poffset, p.name, p.value) + prop = Prop(node, poffset, p.name, p) props_dict[prop.name] = prop poffset = libfdt.fdt_next_property_offset(self._fdt, poffset) diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index 99f4e1a..e475a7e 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -4,7 +4,8 @@ """Tests for the dtb_platdata module -This includes unit tests for some functions and functional tests for +This includes unit tests for some functions and functional tests for the dtoc +tool. """ import collections From a1e0085519b7b34b2db36faa76e13b7f1717f570 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:21 -0600 Subject: [PATCH 19/40] libfdt: Fix the Python pack() function This currently fails to reduce the device-tree bytearray size. Fix this. This stands in for a pending upstream change. Signed-off-by: Simon Glass --- scripts/dtc/pylibfdt/libfdt.i_shipped | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 6774b93..5b38e63 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -442,7 +442,11 @@ class Fdt: Raises: FdtException if any error occurs """ - return check_err(fdt_pack(self._fdt), quiet) + err = check_err(fdt_pack(self._fdt), quiet) + if err: + return err + del self._fdt[self.totalsize():] + return err def getprop(self, nodeoffset, prop_name, quiet=()): """Get a property from a node From c640ed0ce6b029f8cda62f2674a671a9c198410a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:22 -0600 Subject: [PATCH 20/40] libfdt: Add get_property() and del_node() Add support for these functions in the Python binding. This patch stands in for a pending upstream change. Signed-off-by: Simon Glass --- scripts/dtc/pylibfdt/libfdt.i_shipped | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped index 5b38e63..e180ee9 100644 --- a/scripts/dtc/pylibfdt/libfdt.i_shipped +++ b/scripts/dtc/pylibfdt/libfdt.i_shipped @@ -398,6 +398,27 @@ class Fdt: return pdata return Property(pdata[0], pdata[1]) + def get_property(self, nodeoffset, prop_name, quiet=()): + """Obtains a property by name + + Args: + nodeoffset: Offset to the node to check + prop_name: Name of property to get + quiet: Errors to ignore (empty to raise on all errors) + + Returns: + Property object, or None if not found + + Raises: + FdtException on error (e.g. invalid prop_offset or device + tree format) + """ + pdata = check_err_null( + fdt_get_property(self._fdt, nodeoffset, prop_name), quiet) + if isinstance(pdata, (int)): + return pdata + return Property(pdata[0], pdata[1]) + @staticmethod def create_empty_tree(size, quiet=()): """Create an empty device tree ready for use @@ -632,6 +653,17 @@ class Fdt: """ return check_err(fdt_node_offset_by_phandle(self._fdt, phandle), quiet) + def del_node(self, nodeoffset): + """Delete a node + + Args: + nodeoffset: Node offset containing property to delete + + Raises: + FdtError if the node does not exist, or another error occurs + """ + return check_err(fdt_del_node(self._fdt, nodeoffset)) + class Property(bytearray): """Holds a device tree property name and value. From ff1fd6ccde3d166213d1277fa6b6b9685d45044f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:23 -0600 Subject: [PATCH 21/40] binman: Move coverage logic into a new test_util file At present only binman has the logic for determining Python test coverage but this is useful for other tools also. Move it out into a separate file so it can be used by other tools. Signed-off-by: Simon Glass --- tools/binman/binman.py | 29 +++------------------ tools/patman/test_util.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 tools/patman/test_util.py diff --git a/tools/binman/binman.py b/tools/binman/binman.py index 74862b1..52e02ed 100755 --- a/tools/binman/binman.py +++ b/tools/binman/binman.py @@ -26,6 +26,7 @@ sys.path.insert(0, 'scripts/dtc/pylibfdt') import cmdline import command import control +import test_util def RunTests(debug, args): """Run the functional tests and any embedded doctests @@ -78,34 +79,12 @@ def RunTests(debug, args): def RunTestCoverage(): """Run the tests and check that we get 100% coverage""" - # This uses the build output from sandbox_spl to get _libfdt.so - cmd = ('PYTHONPATH=$PYTHONPATH:%s/sandbox_spl/tools python-coverage run ' - '--include "tools/binman/*.py" --omit "*test*,*binman.py" ' - 'tools/binman/binman.py -t' % options.build_dir) - os.system(cmd) - stdout = command.Output('python-coverage', 'report') - lines = stdout.splitlines() - - test_set= set([os.path.basename(line.split()[0]) - for line in lines if '/etype/' in line]) glob_list = glob.glob(os.path.join(our_path, 'etype/*.py')) all_set = set([os.path.splitext(os.path.basename(item))[0] for item in glob_list if '_testing' not in item]) - missing_list = all_set - missing_list.difference_update(test_set) - coverage = lines[-1].split(' ')[-1] - ok = True - if missing_list: - print 'Missing tests for %s' % (', '.join(missing_list)) - print stdout - ok = False - if coverage != '100%': - print stdout - print "Type 'coverage html' to get a report in htmlcov/index.html" - print 'Coverage error: %s, but should be 100%%' % coverage - ok = False - if not ok: - raise ValueError('Test coverage failure') + test_util.RunTestCoverage('tools/binman/binman.py', None, + ['*test*', '*binman.py', 'tools/patman/*', 'tools/dtoc/*'], + options.build_dir, all_set) def RunBinman(options, args): """Main entry point to binman once arguments are parsed diff --git a/tools/patman/test_util.py b/tools/patman/test_util.py new file mode 100644 index 0000000..1a33c99 --- /dev/null +++ b/tools/patman/test_util.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) 2016 Google, Inc +# + +import glob +import os +import sys + +import command + +def RunTestCoverage(prog, filter_fname, exclude_list, build_dir, required=None): + """Run tests and check that we get 100% coverage + + Args: + prog: Program to run (with be passed a '-t' argument to run tests + filter_fname: Normally all *.py files in the program's directory will + be included. If this is not None, then it is used to filter the + list so that only filenames that don't contain filter_fname are + included. + exclude_list: List of file patterns to exclude from the coverage + calculation + build_dir: Build directory, used to locate libfdt.py + required: List of modules which must be in the coverage report + + Raises: + ValueError if the code coverage is not 100% + """ + # This uses the build output from sandbox_spl to get _libfdt.so + path = os.path.dirname(prog) + if filter_fname: + glob_list = glob.glob(os.path.join(path, '*.py')) + glob_list = [fname for fname in glob_list if filter_fname in fname] + else: + glob_list = [] + glob_list += exclude_list + glob_list += ['*libfdt.py', '*site-packages*'] + cmd = ('PYTHONPATH=$PYTHONPATH:%s/sandbox_spl/tools python-coverage run ' + '--omit "%s" %s -t' % (build_dir, ','.join(glob_list), prog)) + os.system(cmd) + stdout = command.Output('python-coverage', 'report') + lines = stdout.splitlines() + if required: + # Convert '/path/to/name.py' just the module name 'name' + 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.difference_update(test_set) + if missing_list: + print 'Missing tests for %s' % (', '.join(missing_list)) + print stdout + ok = False + + coverage = lines[-1].split(' ')[-1] + ok = True + print coverage + if coverage != '100%': + print stdout + print ("Type 'python-coverage html' to get a report in " + 'htmlcov/index.html') + print 'Coverage error: %s, but should be 100%%' % coverage + ok = False + if not ok: + raise ValueError('Test coverage failure') From 2ba987531254dfacb299e88406b3da73ea2d7915 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:24 -0600 Subject: [PATCH 22/40] dtoc: Add some tests for the fdt module At present this module is tested via the dtoc tests. This is a bit painful since the tests are at a higher level and so failures are more difficult to diagnose. Add some tests that exercise the fdt module directly. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 12 ++- tools/dtoc/test_fdt | 1 + tools/dtoc/test_fdt.py | 246 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+), 2 deletions(-) create mode 120000 tools/dtoc/test_fdt create mode 100755 tools/dtoc/test_fdt.py diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index d08b0b5..5cde8c1 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -138,6 +138,7 @@ class Prop: else: return TYPE_INT, val + @classmethod def GetEmpty(self, type): """Get an empty / zero value of the given type @@ -335,12 +336,19 @@ class Fdt: """ return self._fdt - def CheckErr(errnum, msg): + def GetFdtObj(self): + """Get the contents of the FDT + + Returns: + The FDT contents as a libfdt.Fdt object + """ + return self._fdt_obj + + def CheckErr(self, errnum, msg): if errnum: raise ValueError('Error %d: %s: %s' % (errnum, libfdt.fdt_strerror(errnum), msg)) - def GetProps(self, node): """Get all properties from a node. diff --git a/tools/dtoc/test_fdt b/tools/dtoc/test_fdt new file mode 120000 index 0000000..7c3b230 --- /dev/null +++ b/tools/dtoc/test_fdt @@ -0,0 +1 @@ +test_fdt.py \ No newline at end of file diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py new file mode 100755 index 0000000..ba660ca --- /dev/null +++ b/tools/dtoc/test_fdt.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2018 Google, Inc +# Written by Simon Glass +# + +from optparse import OptionParser +import glob +import os +import sys +import unittest + +# Bring in the patman libraries +our_path = os.path.dirname(os.path.realpath(__file__)) +for dirname in ['../patman', '..']: + sys.path.insert(0, os.path.join(our_path, dirname)) + +import command +import fdt +from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL +from fdt_util import fdt32_to_cpu +import libfdt +import test_util +import tools + +class TestFdt(unittest.TestCase): + """Tests for the Fdt module + + This includes unit tests for some functions and functional tests for the fdt + module. + """ + @classmethod + def setUpClass(cls): + tools.PrepareOutputDir(None) + + @classmethod + def tearDownClass(cls): + tools._FinaliseForTest() + + def setUp(self): + self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') + + def testFdt(self): + """Test that we can open an Fdt""" + self.dtb.Scan() + root = self.dtb.GetRoot() + self.assertTrue(isinstance(root, fdt.Node)) + + def testGetNode(self): + """Test the GetNode() method""" + node = self.dtb.GetNode('/spl-test') + self.assertTrue(isinstance(node, fdt.Node)) + node = self.dtb.GetNode('/i2c@0/pmic@9') + self.assertTrue(isinstance(node, fdt.Node)) + self.assertEqual('pmic@9', node.name) + + def testFlush(self): + """Check that we can flush the device tree out to its file""" + fname = self.dtb._fname + with open(fname) as fd: + data = fd.read() + os.remove(fname) + with self.assertRaises(IOError): + open(fname) + self.dtb.Flush() + with open(fname) as fd: + data = fd.read() + + def testPack(self): + """Test that packing a device tree works""" + self.dtb.Pack() + + def testGetFdt(self): + """Tetst that we can access the raw device-tree data""" + self.assertTrue(isinstance(self.dtb.GetFdt(), bytearray)) + + def testGetProps(self): + """Tests obtaining a list of properties""" + node = self.dtb.GetNode('/spl-test') + props = self.dtb.GetProps(node) + self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible', + 'intarray', 'intval', 'longbytearray', + 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'], + sorted(props.keys())) + + def testCheckError(self): + """Tests the ChecKError() function""" + with self.assertRaises(ValueError) as e: + self.dtb.CheckErr(-libfdt.NOTFOUND, 'hello') + self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception)) + + +class TestNode(unittest.TestCase): + """Test operation of the Node class""" + + @classmethod + def setUpClass(cls): + tools.PrepareOutputDir(None) + + @classmethod + def tearDownClass(cls): + tools._FinaliseForTest() + + def setUp(self): + self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') + self.node = self.dtb.GetNode('/spl-test') + + def testOffset(self): + """Tests that we can obtain the offset of a node""" + self.assertTrue(self.node.Offset() > 0) + + def testDelete(self): + """Tests that we can delete a property""" + node2 = self.dtb.GetNode('/spl-test2') + offset1 = node2.Offset() + self.node.DeleteProp('intval') + offset2 = node2.Offset() + self.assertTrue(offset2 < offset1) + self.node.DeleteProp('intarray') + offset3 = node2.Offset() + self.assertTrue(offset3 < offset2) + + def testFindNode(self): + """Tests that we can find a node using the _FindNode() functoin""" + node = self.dtb.GetRoot()._FindNode('i2c@0') + self.assertEqual('i2c@0', node.name) + subnode = node._FindNode('pmic@9') + self.assertEqual('pmic@9', subnode.name) + + +class TestProp(unittest.TestCase): + """Test operation of the Prop class""" + + @classmethod + def setUpClass(cls): + tools.PrepareOutputDir(None) + + @classmethod + def tearDownClass(cls): + tools._FinaliseForTest() + + def setUp(self): + self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') + self.node = self.dtb.GetNode('/spl-test') + self.fdt = self.dtb.GetFdtObj() + + def testGetEmpty(self): + """Tests the GetEmpty() function for the various supported types""" + self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL)) + self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE)) + self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT)) + self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING)) + + def testGetOffset(self): + """Test we can get the offset of a property""" + prop = self.node.props['longbytearray'] + + # Add 12, which is sizeof(struct fdt_property), to get to start of data + offset = prop.GetOffset() + 12 + data = self.dtb._fdt[offset:offset + len(prop.value)] + bytes = [chr(x) for x in data] + self.assertEqual(bytes, prop.value) + + def testWiden(self): + """Test widening of values""" + node2 = self.dtb.GetNode('/spl-test2') + prop = self.node.props['intval'] + + # No action + prop2 = node2.props['intval'] + prop.Widen(prop2) + self.assertEqual(fdt.TYPE_INT, prop.type) + self.assertEqual(1, fdt32_to_cpu(prop.value)) + + # Convert singla value to array + prop2 = self.node.props['intarray'] + prop.Widen(prop2) + self.assertEqual(fdt.TYPE_INT, prop.type) + self.assertTrue(isinstance(prop.value, list)) + + # A 4-byte array looks like a single integer. When widened by a longer + # byte array, it should turn into an array. + prop = self.node.props['longbytearray'] + prop2 = node2.props['longbytearray'] + self.assertFalse(isinstance(prop2.value, list)) + self.assertEqual(4, len(prop2.value)) + prop2.Widen(prop) + self.assertTrue(isinstance(prop2.value, list)) + self.assertEqual(9, len(prop2.value)) + + # Similarly for a string array + prop = self.node.props['stringval'] + prop2 = node2.props['stringarray'] + self.assertFalse(isinstance(prop.value, list)) + self.assertEqual(7, len(prop.value)) + prop.Widen(prop2) + self.assertTrue(isinstance(prop.value, list)) + self.assertEqual(3, len(prop.value)) + + # Enlarging an existing array + prop = self.node.props['stringarray'] + prop2 = node2.props['stringarray'] + self.assertTrue(isinstance(prop.value, list)) + self.assertEqual(2, len(prop.value)) + prop.Widen(prop2) + self.assertTrue(isinstance(prop.value, list)) + self.assertEqual(3, len(prop.value)) + + +def RunTests(args): + """Run all the test we have for the fdt model + + Args: + args: List of positional args provided to fdt. This can hold a test + name to execute (as in 'fdt -t testFdt', for example) + """ + result = unittest.TestResult() + sys.argv = [sys.argv[0]] + test_name = args and args[0] or None + for module in (TestFdt, TestNode, TestProp): + if test_name: + try: + suite = unittest.TestLoader().loadTestsFromName(test_name, module) + except AttributeError: + continue + else: + suite = unittest.TestLoader().loadTestsFromTestCase(module) + suite.run(result) + + print result + for _, err in result.errors: + print err + for _, err in result.failures: + print err + +if __name__ != '__main__': + sys.exit(1) + +parser = OptionParser() +parser.add_option('-t', '--test', action='store_true', dest='test', + default=False, help='run tests') +(options, args) = parser.parse_args() + +# Run our meagre tests +if options.test: + RunTests(args) From 57f0bc428aeecbc30a89a10be05fa69fd3e64aef Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:25 -0600 Subject: [PATCH 23/40] dtoc: Update tests to write failures to /tmp When a test fails due to an output mismatch (e.g. due to a new property being adding to a test file) it is currently hard to update the test to the new output. In particular the tabs in the file are written as \t in the Python tests. To make this easier, write both the expected and actual results to /tmp to allow use of meld, and copying into the test. Signed-off-by: Simon Glass --- tools/dtoc/test_dtoc.py | 56 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index e475a7e..ce6d258 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -68,6 +68,34 @@ class TestDtoc(unittest.TestCase): def tearDownClass(cls): tools._RemoveOutputDir() + def _WritePythonString(self, fname, data): + """Write a string with tabs expanded as done in this Python file + + Args: + fname: Filename to write to + data: Raw string to convert + """ + data = data.replace('\t', '\\t') + with open(fname, 'w') as fd: + fd.write(data) + + def _CheckStrings(self, expected, actual): + """Check that a string matches its expected value + + If the strings do not match, they are written to the /tmp directory in + the same Python format as is used here in the test. This allows for + easy comparison and update of the tests. + + Args: + expected: Expected string + actual: Actual string + """ + if expected != actual: + self._WritePythonString('/tmp/binman.expected', expected) + self._WritePythonString('/tmp/binman.actual', actual) + print 'Failures written to /tmp/binman.{expected,actual}' + self.assertEquals(expected, actual) + def test_name(self): """Test conversion of device tree names to C identifiers""" self.assertEqual('serial_at_0x12', conv_name_to_c('serial@0x12')) @@ -138,7 +166,7 @@ class TestDtoc(unittest.TestCase): dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_sandbox_i2c_test { }; struct dtd_sandbox_pmic_test { @@ -162,7 +190,7 @@ struct dtd_sandbox_spl_test_2 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_sandbox_spl_test dtv_spl_test = { \t.bytearray\t\t= {0x6, 0x0, 0x0}, \t.byteval\t\t= 0x5, @@ -240,7 +268,7 @@ U_BOOT_DEVICE(pmic_at_9) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_source { \tstruct phandle_2_arg clocks[4]; }; @@ -252,7 +280,7 @@ struct dtd_target { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_target dtv_phandle_target = { \t.intval\t\t\t= 0x0, }; @@ -302,7 +330,7 @@ U_BOOT_DEVICE(phandle_source) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_compat1 { \tfdt32_t\t\tintval; }; @@ -313,7 +341,7 @@ struct dtd_compat1 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_compat1 dtv_spl_test = { \t.intval\t\t\t= 0x1, }; @@ -332,7 +360,7 @@ U_BOOT_DEVICE(spl_test) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_test1 { \tfdt64_t\t\treg[2]; }; @@ -347,7 +375,7 @@ struct dtd_test3 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_test1 dtv_test1 = { \t.reg\t\t\t= {0x1234, 0x5678}, }; @@ -384,7 +412,7 @@ U_BOOT_DEVICE(test3) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_test1 { \tfdt32_t\t\treg[2]; }; @@ -396,7 +424,7 @@ struct dtd_test2 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_test1 dtv_test1 = { \t.reg\t\t\t= {0x1234, 0x5678}, }; @@ -424,7 +452,7 @@ U_BOOT_DEVICE(test2) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_test1 { \tfdt64_t\t\treg[2]; }; @@ -439,7 +467,7 @@ struct dtd_test3 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_test1 dtv_test1 = { \t.reg\t\t\t= {0x123400000000, 0x5678}, }; @@ -476,7 +504,7 @@ U_BOOT_DEVICE(test3) = { dtb_platdata.run_steps(['struct'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(HEADER + ''' + self._CheckStrings(HEADER + ''' struct dtd_test1 { \tfdt64_t\t\treg[2]; }; @@ -491,7 +519,7 @@ struct dtd_test3 { dtb_platdata.run_steps(['platdata'], dtb_file, False, output) with open(output) as infile: data = infile.read() - self.assertEqual(C_HEADER + ''' + self._CheckStrings(C_HEADER + ''' static struct dtd_test1 dtv_test1 = { \t.reg\t\t\t= {0x1234, 0x567800000000}, }; From 117f57b75aee2e145d2d23107f600d869b04231a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:26 -0600 Subject: [PATCH 24/40] dtoc: Make use of the new pylibfdt methods Now that pylibfdt supports a fuller API we don't need to directly call the libfdt stubs. Update the code to use the Fdt methods instead. Some other cases remain which will be tidied up in a later commit, since they need larger changes. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index 5cde8c1..18cde26 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -10,6 +10,7 @@ import sys import fdt_util import libfdt +from libfdt import QUIET_NOTFOUND # This deals with a device tree, presenting it as an assortment of Node and # Prop objects, representing nodes and properties, respectively. This file @@ -211,22 +212,22 @@ class Node: This fills in the props and subnodes properties, recursively searching into subnodes so that the entire tree is built. """ + fdt_obj = self._fdt._fdt_obj self.props = self._fdt.GetProps(self) - phandle = self.props.get('phandle') + phandle = fdt_obj.get_phandle(self.Offset()) if phandle: - val = fdt_util.fdt32_to_cpu(phandle.value) - self._fdt.phandle_to_node[val] = self + self._fdt.phandle_to_node[phandle] = self - offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset()) + offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) while offset >= 0: sep = '' if self.path[-1] == '/' else '/' - name = self._fdt._fdt_obj.get_name(offset) + name = fdt_obj.get_name(offset) path = self.path + sep + name node = Node(self._fdt, self, offset, name, path) self.subnodes.append(node) node.Scan() - offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) + offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) def Refresh(self, my_offset): """Fix up the _offset for each node, recursively @@ -324,9 +325,8 @@ class Fdt: When nodes and properties shrink or are deleted, wasted space can build up in the device tree binary. """ - CheckErr(libfdt.fdt_pack(self._fdt), 'pack') - fdt_len = libfdt.fdt_totalsize(self._fdt) - del self._fdt[fdt_len:] + CheckErr(self._fdt_obj.pack(), 'pack') + self.Invalidate() def GetFdt(self): """Get the contents of the FDT @@ -363,13 +363,15 @@ class Fdt: ValueError: if the node does not exist. """ props_dict = {} - poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset) + poffset = self._fdt_obj.first_property_offset(node._offset, + QUIET_NOTFOUND) while poffset >= 0: p = self._fdt_obj.get_property_by_offset(poffset) prop = Prop(node, poffset, p.name, p) props_dict[prop.name] = prop - poffset = libfdt.fdt_next_property_offset(self._fdt, poffset) + poffset = self._fdt_obj.next_property_offset(poffset, + QUIET_NOTFOUND) return props_dict def Invalidate(self): @@ -395,7 +397,7 @@ class Fdt: Returns: Position of @offset within the device tree binary """ - return libfdt.fdt_off_dt_struct(self._fdt) + offset + return self._fdt_obj.off_dt_struct() + offset @classmethod def Node(self, fdt, parent, offset, name, path): From 960662404f492e7088e5a91833dfb0c19ef5450e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:27 -0600 Subject: [PATCH 25/40] dtoc: Drop use of a local dtb buffer At present the Fdt class has its own copy of the device tree. This is confusing an unnecessary now that pylibfdt has its own. Drop it and provide access functions to the buffer. This allows us to move the rest of the implementation to use pylibfdt methods instead of directly calling libfdt stubs. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 16 ++++++++-------- tools/dtoc/test_fdt.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index 18cde26..e24acf1 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -235,12 +235,13 @@ class Node: Note: This does not take account of property offsets - these will not be updated. """ + fdt_obj = self._fdt._fdt_obj if self._offset != my_offset: self._offset = my_offset - offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset) + offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) for subnode in self.subnodes: subnode.Refresh(offset) - offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) + offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) def DeleteProp(self, prop_name): """Delete a property of a node @@ -252,7 +253,7 @@ class Node: Raises: ValueError if the property does not exist """ - CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name), + CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), "Node '%s': delete property: '%s'" % (self.path, prop_name)) del self.props[prop_name] self._fdt.Invalidate() @@ -272,8 +273,7 @@ class Fdt: self._fname = fdt_util.EnsureCompiled(self._fname) with open(self._fname) as fd: - self._fdt = bytearray(fd.read()) - self._fdt_obj = libfdt.Fdt(self._fdt) + self._fdt_obj = libfdt.Fdt(fd.read()) def Scan(self, root='/'): """Scan a device tree, building up a tree of Node objects @@ -317,7 +317,7 @@ class Fdt: If the device tree has changed in memory, write it back to the file. """ with open(self._fname, 'wb') as fd: - fd.write(self._fdt) + fd.write(self._fdt_obj.as_bytearray()) def Pack(self): """Pack the device tree down to its minimum size @@ -328,13 +328,13 @@ class Fdt: CheckErr(self._fdt_obj.pack(), 'pack') self.Invalidate() - def GetFdt(self): + def GetContents(self): """Get the contents of the FDT Returns: The FDT contents as a string of bytes """ - return self._fdt + return self._fdt_obj.as_bytearray() def GetFdtObj(self): """Get the contents of the FDT diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index ba660ca..daa9d12 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -72,7 +72,7 @@ class TestFdt(unittest.TestCase): def testGetFdt(self): """Tetst that we can access the raw device-tree data""" - self.assertTrue(isinstance(self.dtb.GetFdt(), bytearray)) + self.assertTrue(isinstance(self.dtb.GetContents(), bytearray)) def testGetProps(self): """Tests obtaining a list of properties""" @@ -157,7 +157,7 @@ class TestProp(unittest.TestCase): # Add 12, which is sizeof(struct fdt_property), to get to start of data offset = prop.GetOffset() + 12 - data = self.dtb._fdt[offset:offset + len(prop.value)] + data = self.dtb.GetContents()[offset:offset + len(prop.value)] bytes = [chr(x) for x in data] self.assertEqual(bytes, prop.value) From 2a2d91d0d6fbe4f5c0f336a75ff42dcf7b49a774 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:28 -0600 Subject: [PATCH 26/40] dtoc: Update fdt tests to increase code coverage At present only some of the fdt functionality is tested. Add more tests to cover the rest of it. Also turn on test coverage, which is now 100% with a small exclusion for a Python 3 feature. Signed-off-by: Simon Glass --- tools/dtoc/dtoc_test_simple.dts | 1 + tools/dtoc/fdt.py | 12 ---- tools/dtoc/fdt_util.py | 25 +++++--- tools/dtoc/test_dtoc.py | 2 + tools/dtoc/test_fdt.py | 134 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 150 insertions(+), 24 deletions(-) diff --git a/tools/dtoc/dtoc_test_simple.dts b/tools/dtoc/dtoc_test_simple.dts index 895cc1f..165680b 100644 --- a/tools/dtoc/dtoc_test_simple.dts +++ b/tools/dtoc/dtoc_test_simple.dts @@ -21,6 +21,7 @@ longbytearray = [09 0a 0b 0c 0d 0e 0f 10 11]; stringval = "message"; stringarray = "multi-word", "message"; + notstring = [20 21 22 10 00]; }; spl-test2 { diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index e24acf1..fd016bb 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -49,13 +49,6 @@ class Prop: return self.type, self.value = self.BytesToValue(bytes) - def GetPhandle(self): - """Get a (single) phandle value from a property - - Gets the phandle valuie from a property and returns it as an integer - """ - return fdt_util.fdt32_to_cpu(self.value[:4]) - def Widen(self, newprop): """Figure out which property type is more general @@ -344,11 +337,6 @@ class Fdt: """ return self._fdt_obj - def CheckErr(self, errnum, msg): - if errnum: - raise ValueError('Error %d: %s: %s' % - (errnum, libfdt.fdt_strerror(errnum), msg)) - def GetProps(self, node): """Get all properties from a node. diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py index 2d09649..88fc318 100644 --- a/tools/dtoc/fdt_util.py +++ b/tools/dtoc/fdt_util.py @@ -13,6 +13,14 @@ import tempfile import command import tools +VERSION3 = sys.version_info > (3, 0) + +def get_plain_bytes(val): + """Handle Python 3 strings""" + if isinstance(val, bytes): + val = val.decode('utf-8') + return val.encode('raw_unicode_escape') + def fdt32_to_cpu(val): """Convert a device tree cell to an integer @@ -22,10 +30,9 @@ def fdt32_to_cpu(val): Return: A native-endian integer value """ - if sys.version_info > (3, 0): - if isinstance(val, bytes): - val = val.decode('utf-8') - val = val.encode('raw_unicode_escape') + if VERSION3: + # This code is not reached in Python 2 + val = get_plain_bytes(val) # pragma: no cover return struct.unpack('>I', val)[0] def fdt_cells_to_cpu(val, cells): @@ -86,10 +93,10 @@ def GetInt(node, propname, default=None): prop = node.props.get(propname) if not prop: return default - value = fdt32_to_cpu(prop.value) - if type(value) == type(list): - raise ValueError("Node '%s' property '%' has list value: expecting" + if isinstance(prop.value, list): + raise ValueError("Node '%s' property '%s' has list value: expecting " "a single integer" % (node.name, propname)) + value = fdt32_to_cpu(prop.value) return value def GetString(node, propname, default=None): @@ -97,8 +104,8 @@ def GetString(node, propname, default=None): if not prop: return default value = prop.value - if type(value) == type(list): - raise ValueError("Node '%s' property '%' has list value: expecting" + if isinstance(value, list): + raise ValueError("Node '%s' property '%s' has list value: expecting " "a single string" % (node.name, propname)) return value diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index ce6d258..20fea52 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -180,6 +180,7 @@ struct dtd_sandbox_spl_test { \tfdt32_t\t\tintarray[4]; \tfdt32_t\t\tintval; \tunsigned char\tlongbytearray[9]; +\tunsigned char\tnotstring[5]; \tconst char *\tstringarray[3]; \tconst char *\tstringval; }; @@ -195,6 +196,7 @@ static struct dtd_sandbox_spl_test dtv_spl_test = { \t.bytearray\t\t= {0x6, 0x0, 0x0}, \t.byteval\t\t= 0x5, \t.intval\t\t\t= 0x1, +\t.notstring\t\t= {0x20, 0x21, 0x22, 0x10, 0x0}, \t.longbytearray\t\t= {0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, \t\t0x11}, \t.stringval\t\t= "message", diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index daa9d12..d44e4dd 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -18,6 +18,7 @@ for dirname in ['../patman', '..']: import command import fdt from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL +import fdt_util from fdt_util import fdt32_to_cpu import libfdt import test_util @@ -53,6 +54,7 @@ class TestFdt(unittest.TestCase): node = self.dtb.GetNode('/i2c@0/pmic@9') self.assertTrue(isinstance(node, fdt.Node)) self.assertEqual('pmic@9', node.name) + self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing')) def testFlush(self): """Check that we can flush the device tree out to its file""" @@ -79,14 +81,14 @@ class TestFdt(unittest.TestCase): node = self.dtb.GetNode('/spl-test') props = self.dtb.GetProps(node) self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible', - 'intarray', 'intval', 'longbytearray', + 'intarray', 'intval', 'longbytearray', 'notstring', 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'], sorted(props.keys())) def testCheckError(self): """Tests the ChecKError() function""" with self.assertRaises(ValueError) as e: - self.dtb.CheckErr(-libfdt.NOTFOUND, 'hello') + fdt.CheckErr(-libfdt.NOTFOUND, 'hello') self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception)) @@ -119,6 +121,8 @@ class TestNode(unittest.TestCase): self.node.DeleteProp('intarray') offset3 = node2.Offset() self.assertTrue(offset3 < offset2) + with self.assertRaises(libfdt.FdtException): + self.node.DeleteProp('missing') def testFindNode(self): """Tests that we can find a node using the _FindNode() functoin""" @@ -126,6 +130,7 @@ class TestNode(unittest.TestCase): self.assertEqual('i2c@0', node.name) subnode = node._FindNode('pmic@9') self.assertEqual('pmic@9', subnode.name) + self.assertEqual(None, node._FindNode('missing')) class TestProp(unittest.TestCase): @@ -144,6 +149,58 @@ class TestProp(unittest.TestCase): self.node = self.dtb.GetNode('/spl-test') self.fdt = self.dtb.GetFdtObj() + def testPhandle(self): + dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') + node = dtb.GetNode('/phandle-source') + + def _ConvertProp(self, prop_name): + """Helper function to look up a property in self.node and return it + + Args: + Property name to find + + Return fdt.Prop object for this property + """ + p = self.fdt.get_property(self.node.Offset(), prop_name) + return fdt.Prop(self.node, -1, prop_name, p) + + def testMakeProp(self): + """Test we can convert all the the types that are supported""" + prop = self._ConvertProp('boolval') + self.assertEqual(fdt.TYPE_BOOL, prop.type) + self.assertEqual(True, prop.value) + + prop = self._ConvertProp('intval') + self.assertEqual(fdt.TYPE_INT, prop.type) + self.assertEqual(1, fdt32_to_cpu(prop.value)) + + prop = self._ConvertProp('intarray') + self.assertEqual(fdt.TYPE_INT, prop.type) + val = [fdt32_to_cpu(val) for val in prop.value] + self.assertEqual([2, 3, 4], val) + + prop = self._ConvertProp('byteval') + self.assertEqual(fdt.TYPE_BYTE, prop.type) + self.assertEqual(5, ord(prop.value)) + + prop = self._ConvertProp('longbytearray') + self.assertEqual(fdt.TYPE_BYTE, prop.type) + val = [ord(val) for val in prop.value] + self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val) + + prop = self._ConvertProp('stringval') + self.assertEqual(fdt.TYPE_STRING, prop.type) + self.assertEqual('message', prop.value) + + prop = self._ConvertProp('stringarray') + self.assertEqual(fdt.TYPE_STRING, prop.type) + self.assertEqual(['multi-word', 'message'], prop.value) + + prop = self._ConvertProp('notstring') + self.assertEqual(fdt.TYPE_BYTE, prop.type) + val = [ord(val) for val in prop.value] + self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val) + def testGetEmpty(self): """Tests the GetEmpty() function for the various supported types""" self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL)) @@ -207,6 +264,71 @@ class TestProp(unittest.TestCase): self.assertEqual(3, len(prop.value)) +class TestFdtUtil(unittest.TestCase): + """Tests for the fdt_util module + + This module will likely be mostly replaced at some point, once upstream + libfdt has better Python support. For now, this provides tests for current + functionality. + """ + @classmethod + def setUpClass(cls): + tools.PrepareOutputDir(None) + + def setUp(self): + self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') + self.node = self.dtb.GetNode('/spl-test') + + def testGetInt(self): + self.assertEqual(1, fdt_util.GetInt(self.node, 'intval')) + self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) + + with self.assertRaises(ValueError) as e: + self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray')) + self.assertIn("property 'intarray' has list value: expecting a single " + 'integer', str(e.exception)) + + def testGetString(self): + self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) + self.assertEqual('test', fdt_util.GetString(self.node, 'missing', + 'test')) + + with self.assertRaises(ValueError) as e: + self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray')) + self.assertIn("property 'stringarray' has list value: expecting a " + 'single string', str(e.exception)) + + def testGetBool(self): + self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) + self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) + self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True)) + self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False)) + + def testFdtCellsToCpu(self): + val = self.node.props['intarray'].value + self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) + self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) + + dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts') + node2 = dtb2.GetNode('/test1') + val = node2.props['reg'].value + self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) + + def testEnsureCompiled(self): + """Test a degenerate case of this function""" + dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts') + self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) + + def testGetPlainBytes(self): + self.assertEqual('fred', fdt_util.get_plain_bytes('fred')) + + +def RunTestCoverage(): + """Run the tests and check that we get 100% coverage""" + test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None, + ['tools/patman/*.py', '*test_fdt.py'], options.build_dir) + + def RunTests(args): """Run all the test we have for the fdt model @@ -217,7 +339,7 @@ def RunTests(args): result = unittest.TestResult() sys.argv = [sys.argv[0]] test_name = args and args[0] or None - for module in (TestFdt, TestNode, TestProp): + for module in (TestFdt, TestNode, TestProp, TestFdtUtil): if test_name: try: suite = unittest.TestLoader().loadTestsFromName(test_name, module) @@ -237,10 +359,16 @@ if __name__ != '__main__': sys.exit(1) parser = OptionParser() +parser.add_option('-B', '--build-dir', type='string', default='b', + help='Directory containing the build output') parser.add_option('-t', '--test', action='store_true', dest='test', default=False, help='run tests') +parser.add_option('-T', '--test-coverage', action='store_true', + default=False, help='run tests and check for 100% coverage') (options, args) = parser.parse_args() # Run our meagre tests if options.test: RunTests(args) +elif options.test_coverage: + RunTestCoverage() From f9b88b3a5d7ff18760c2c5a0cb596ec8c577706e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:29 -0600 Subject: [PATCH 27/40] dtoc: Keep track of property offsets At present the Fdt class does not keep track of property offsets if they change due to removal of properties. Update the code to handle this, and add a test. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 20 ++++++++++++++++ tools/dtoc/test_fdt.py | 65 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index fd016bb..274c142 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -49,6 +49,9 @@ class Prop: return self.type, self.value = self.BytesToValue(bytes) + def RefreshOffset(self, poffset): + self._offset = poffset + def Widen(self, newprop): """Figure out which property type is more general @@ -154,6 +157,7 @@ class Prop: Returns: The offset of the property (struct fdt_property) within the file """ + self._node._fdt.CheckCache() return self._node._fdt.GetStructOffset(self._offset) class Node: @@ -233,8 +237,23 @@ class Node: self._offset = my_offset offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) for subnode in self.subnodes: + if subnode.name != fdt_obj.get_name(offset): + raise ValueError('Internal error, node name mismatch %s != %s' % + (subnode.name, fdt_obj.get_name(offset))) subnode.Refresh(offset) offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) + if offset != -libfdt.FDT_ERR_NOTFOUND: + raise ValueError('Internal error, offset == %d' % offset) + + poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) + while poffset >= 0: + p = fdt_obj.get_property_by_offset(poffset) + prop = self.props.get(p.name) + if not prop: + raise ValueError("Internal error, property '%s' missing, " + 'offset %d' % (p.name, poffset)) + prop.RefreshOffset(poffset) + poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) def DeleteProp(self, prop_name): """Delete a property of a node @@ -278,6 +297,7 @@ class Fdt: TODO(sjg@chromium.org): Implement the 'root' parameter """ + self._cached_offsets = True self._root = self.Node(self, None, 0, '/', '/') self._root.Scan() diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index d44e4dd..e57298d 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -24,6 +24,30 @@ import libfdt import test_util import tools +def _GetPropertyValue(dtb, node, prop_name): + """Low-level function to get the property value based on its offset + + This looks directly in the device tree at the property's offset to find + its value. It is useful as a check that the property is in the correct + place. + + Args: + node: Node to look in + prop_name: Property name to find + + Returns: + Tuple: + Prop object found + Value of property as a string (found using property offset) + """ + prop = node.props[prop_name] + + # Add 12, which is sizeof(struct fdt_property), to get to start of data + offset = prop.GetOffset() + 12 + data = dtb.GetContents()[offset:offset + len(prop.value)] + return prop, [chr(x) for x in data] + + class TestFdt(unittest.TestCase): """Tests for the Fdt module @@ -124,6 +148,12 @@ class TestNode(unittest.TestCase): with self.assertRaises(libfdt.FdtException): self.node.DeleteProp('missing') + def testDeleteGetOffset(self): + """Test that property offset update when properties are deleted""" + self.node.DeleteProp('intval') + prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') + self.assertEqual(prop.value, value) + def testFindNode(self): """Tests that we can find a node using the _FindNode() functoin""" node = self.dtb.GetRoot()._FindNode('i2c@0') @@ -132,6 +162,32 @@ class TestNode(unittest.TestCase): self.assertEqual('pmic@9', subnode.name) self.assertEqual(None, node._FindNode('missing')) + def testRefreshMissingNode(self): + """Test refreshing offsets when an extra node is present in dtb""" + # Delete it from our tables, not the device tree + del self.dtb._root.subnodes[-1] + with self.assertRaises(ValueError) as e: + self.dtb.Refresh() + self.assertIn('Internal error, offset', str(e.exception)) + + def testRefreshExtraNode(self): + """Test refreshing offsets when an expected node is missing""" + # Delete it from the device tre, not our tables + self.dtb.GetFdtObj().del_node(self.node.Offset()) + with self.assertRaises(ValueError) as e: + self.dtb.Refresh() + self.assertIn('Internal error, node name mismatch ' + 'spl-test != spl-test2', str(e.exception)) + + def testRefreshMissingProp(self): + """Test refreshing offsets when an extra property is present in dtb""" + # Delete it from our tables, not the device tree + del self.node.props['notstring'] + with self.assertRaises(ValueError) as e: + self.dtb.Refresh() + self.assertIn("Internal error, property 'notstring' missing, offset ", + str(e.exception)) + class TestProp(unittest.TestCase): """Test operation of the Prop class""" @@ -210,13 +266,8 @@ class TestProp(unittest.TestCase): def testGetOffset(self): """Test we can get the offset of a property""" - prop = self.node.props['longbytearray'] - - # Add 12, which is sizeof(struct fdt_property), to get to start of data - offset = prop.GetOffset() + 12 - data = self.dtb.GetContents()[offset:offset + len(prop.value)] - bytes = [chr(x) for x in data] - self.assertEqual(bytes, prop.value) + prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') + self.assertEqual(prop.value, value) def testWiden(self): """Test widening of values""" From b9066ffc136afd2e46e8d033c4edce98f5557afc Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:30 -0600 Subject: [PATCH 28/40] dtoc: Fix Fdt.GetNode() to handle a missing node At present the algortihm is not correct since it will return the root node if the requested node is not found and there are no slashes in the requested node name. Fix this and add a test. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 5 ++++- tools/dtoc/test_fdt.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index 274c142..c1d04d4 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -318,7 +318,10 @@ class Fdt: Node object, or None if not found """ node = self._root - for part in path.split('/')[1:]: + parts = path.split('/') + if len(parts) < 2: + return None + for part in parts[1:]: node = node._FindNode(part) if not node: return None diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index e57298d..9fef8ed 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -205,6 +205,9 @@ class TestProp(unittest.TestCase): self.node = self.dtb.GetNode('/spl-test') self.fdt = self.dtb.GetFdtObj() + def testMissingNode(self): + self.assertEqual(None, self.dtb.GetNode('missing')) + def testPhandle(self): dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') node = dtb.GetNode('/phandle-source') From 760b7170c5e44c771397eaf57b7197b621373809 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:31 -0600 Subject: [PATCH 29/40] dtoc: Fix properties with a single zero-arg phandle At present a property with a single phandle looks like an integer value to dtoc. Correct this by adjusting it in the phandle-processing code. Add a test for this. Signed-off-by: Simon Glass --- tools/dtoc/dtb_platdata.py | 12 ++++++++---- tools/dtoc/dtoc_test_phandle.dts | 6 ++++++ tools/dtoc/test_dtoc.py | 10 ++++++++++ tools/dtoc/test_fdt.py | 4 +++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py index 2f7302e..b1323ae 100644 --- a/tools/dtoc/dtb_platdata.py +++ b/tools/dtoc/dtb_platdata.py @@ -211,15 +211,21 @@ class DtbPlatdata(object): Number of argument cells is this is a phandle, else None """ if prop.name in ['clocks']: + if not isinstance(prop.value, list): + prop.value = [prop.value] val = prop.value - if not isinstance(val, list): - val = [val] i = 0 max_args = 0 args = [] while i < len(val): phandle = fdt_util.fdt32_to_cpu(val[i]) + # If we get to the end of the list, stop. This can happen + # since some nodes have more phandles in the list than others, + # but we allocate enough space for the largest list. So those + # nodes with shorter lists end up with zeroes at the end. + if not phandle: + break target = self._fdt.phandle_to_node.get(phandle) if not target: raise ValueError("Cannot parse '%s' in node '%s'" % @@ -400,8 +406,6 @@ class DtbPlatdata(object): continue info = self.get_phandle_argc(prop, node.name) if info: - if not isinstance(prop.value, list): - prop.value = [prop.value] # Process the list as pairs of (phandle, id) pos = 0 for args in info.args: diff --git a/tools/dtoc/dtoc_test_phandle.dts b/tools/dtoc/dtoc_test_phandle.dts index 91dfec5..a71acff 100644 --- a/tools/dtoc/dtoc_test_phandle.dts +++ b/tools/dtoc/dtoc_test_phandle.dts @@ -33,4 +33,10 @@ compatible = "source"; clocks = <&phandle &phandle_1 11 &phandle_2 12 13 &phandle>; }; + + phandle-source2 { + u-boot,dm-pre-reloc; + compatible = "source"; + clocks = <&phandle>; + }; }; diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index 20fea52..11cac3f 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -323,6 +323,16 @@ U_BOOT_DEVICE(phandle_source) = { \t.platdata_size\t= sizeof(dtv_phandle_source), }; +static struct dtd_source dtv_phandle_source2 = { +\t.clocks\t\t\t= { +\t\t\t{&dtv_phandle_target, {}},}, +}; +U_BOOT_DEVICE(phandle_source2) = { +\t.name\t\t= "source", +\t.platdata\t= &dtv_phandle_source2, +\t.platdata_size\t= sizeof(dtv_phandle_source2), +}; + ''', data) def test_aliases(self): diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 9fef8ed..49d188b 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -210,7 +210,9 @@ class TestProp(unittest.TestCase): def testPhandle(self): dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') - node = dtb.GetNode('/phandle-source') + node = dtb.GetNode('/phandle-source2') + prop = node.props['clocks'] + self.assertTrue(fdt32_to_cpu(prop.value) > 0) def _ConvertProp(self, prop_name): """Helper function to look up a property in self.node and return it From dfe5f5b97d369b0fcf24c792b0fc83661aa24afa Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:32 -0600 Subject: [PATCH 30/40] dtoc: Fix some minor errors Fix some comments and a printf string which is incorrect. Signed-off-by: Simon Glass --- tools/dtoc/dtb_platdata.py | 3 ++- tools/dtoc/dtoc.py | 4 ++-- tools/dtoc/fdt.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py index b1323ae..6cb1259 100644 --- a/tools/dtoc/dtb_platdata.py +++ b/tools/dtoc/dtb_platdata.py @@ -316,7 +316,8 @@ class DtbPlatdata(object): total = na + ns if reg.type != fdt.TYPE_INT: - raise ValueError("Node '%s' reg property is not an int") + raise ValueError("Node '%s' reg property is not an int" % + node.name) if len(reg.value) % total: raise ValueError("Node '%s' reg property has %d cells " 'which is not a multiple of na + ns = %d + %d)' % diff --git a/tools/dtoc/dtoc.py b/tools/dtoc/dtoc.py index c891b06..2e6a4db 100755 --- a/tools/dtoc/dtoc.py +++ b/tools/dtoc/dtoc.py @@ -40,8 +40,8 @@ def run_tests(args): """Run all the test we have for dtoc Args: - args: List of positional args provided to binman. This can hold a test - name to execute (as in 'binman -t testSections', for example) + args: List of positional args provided to dtoc. This can hold a test + name to execute (as in 'dtoc -t test_empty_file', for example) """ import test_dtoc diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index c1d04d4..e7703c1 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -428,7 +428,7 @@ class Fdt: return node def FdtScan(fname): - """Returns a new Fdt object from the implementation we are using""" + """Returns a new Fdt object""" dtb = Fdt(fname) dtb.Scan() return dtb From ba765217ed0258fa0ecd5cb77c6c4c171624c4ad Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:33 -0600 Subject: [PATCH 31/40] dtoc: Add a test for code coverage Add a -T option to run a code-coverage test on dtoc. At present this is about 96%. Future work will increase it to 100%. Signed-off-by: Simon Glass --- tools/dtoc/dtoc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/dtoc/dtoc.py b/tools/dtoc/dtoc.py index 2e6a4db..827094e 100755 --- a/tools/dtoc/dtoc.py +++ b/tools/dtoc/dtoc.py @@ -35,6 +35,7 @@ our_path = os.path.dirname(os.path.realpath(__file__)) sys.path.append(os.path.join(our_path, '../patman')) import dtb_platdata +import test_util def run_tests(args): """Run all the test we have for dtoc @@ -64,10 +65,19 @@ def run_tests(args): for _, err in result.failures: print err +def RunTestCoverage(): + """Run the tests and check that we get 100% coverage""" + sys.argv = [sys.argv[0]] + test_util.RunTestCoverage('tools/dtoc/dtoc.py', '/dtoc.py', + ['tools/patman/*.py', '*/fdt*', '*test*'], options.build_dir) + + if __name__ != '__main__': sys.exit(1) parser = OptionParser() +parser.add_option('-B', '--build-dir', type='string', default='b', + help='Directory containing the build output') parser.add_option('-d', '--dtb-file', action='store', help='Specify the .dtb input file') parser.add_option('--include-disabled', action='store_true', @@ -76,12 +86,17 @@ parser.add_option('-o', '--output', action='store', default='-', help='Select output filename') parser.add_option('-t', '--test', action='store_true', dest='test', default=False, help='run tests') +parser.add_option('-T', '--test-coverage', action='store_true', + default=False, help='run tests and check for 100% coverage') (options, args) = parser.parse_args() # Run our meagre tests if options.test: run_tests(args) +elif options.test_coverage: + RunTestCoverage() + else: dtb_platdata.run_steps(args, options.dtb_file, options.include_disabled, options.output) From c3f9454103462e2cfcc5a9120b893f4ea250650a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:34 -0600 Subject: [PATCH 32/40] binman: Move capture_sys_output() to test_util This function is useful in various tests. Move it into the common test utility module. Signed-off-by: Simon Glass --- tools/binman/elf_test.py | 22 ++-------------------- tools/binman/image_test.py | 2 +- tools/patman/test_util.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py index fb6e451..9c8f1fe 100644 --- a/tools/binman/elf_test.py +++ b/tools/binman/elf_test.py @@ -4,33 +4,15 @@ # # Test for the elf module -from contextlib import contextmanager import os import sys import unittest -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - import elf +import test_util binman_dir = os.path.dirname(os.path.realpath(sys.argv[0])) -# Use this to suppress stdout/stderr output: -# with capture_sys_output() as (stdout, stderr) -# ...do something... -@contextmanager -def capture_sys_output(): - capture_out, capture_err = StringIO(), StringIO() - old_out, old_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = capture_out, capture_err - yield capture_out, capture_err - finally: - sys.stdout, sys.stderr = old_out, old_err - class FakeEntry: def __init__(self, contents_size): @@ -110,7 +92,7 @@ class TestElf(unittest.TestCase): entry = FakeEntry(20) section = FakeSection() elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms') - with capture_sys_output() as (stdout, stderr): + with test_util.capture_sys_output() as (stdout, stderr): syms = elf.LookupAndWriteSymbols(elf_fname, entry, section) elf.debug = False self.assertTrue(len(stdout.getvalue()) > 0) diff --git a/tools/binman/image_test.py b/tools/binman/image_test.py index 45dd237..3775e1a 100644 --- a/tools/binman/image_test.py +++ b/tools/binman/image_test.py @@ -7,7 +7,7 @@ import unittest from image import Image -from elf_test import capture_sys_output +from test_util import capture_sys_output class TestImage(unittest.TestCase): def testInvalidFormat(self): diff --git a/tools/patman/test_util.py b/tools/patman/test_util.py index 1a33c99..0e79af8 100644 --- a/tools/patman/test_util.py +++ b/tools/patman/test_util.py @@ -3,12 +3,19 @@ # Copyright (c) 2016 Google, Inc # +from contextlib import contextmanager import glob import os import sys import command +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + + def RunTestCoverage(prog, filter_fname, exclude_list, build_dir, required=None): """Run tests and check that we get 100% coverage @@ -62,3 +69,17 @@ def RunTestCoverage(prog, filter_fname, exclude_list, build_dir, required=None): ok = False if not ok: raise ValueError('Test coverage failure') + + +# Use this to suppress stdout/stderr output: +# with capture_sys_output() as (stdout, stderr) +# ...do something... +@contextmanager +def capture_sys_output(): + capture_out, capture_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = capture_out, capture_err + yield capture_out, capture_err + finally: + sys.stdout, sys.stderr = old_out, old_err From 8512ea2e359cf5217183fc87326a015caab1994d Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:35 -0600 Subject: [PATCH 33/40] dtoc: Increase code coverage to 100% Add more tests to increase dtoc code coverage to 100%. Correct a whitespace error in some test .dts files at the same time. Signed-off-by: Simon Glass --- tools/dtoc/dtoc_test_add_prop.dts | 24 ++++++ tools/dtoc/dtoc_test_addr32_64.dts | 2 +- tools/dtoc/dtoc_test_addr64_32.dts | 2 +- tools/dtoc/dtoc_test_bad_reg.dts | 17 ++++ tools/dtoc/dtoc_test_bad_reg2.dts | 17 ++++ tools/dtoc/dtoc_test_phandle_bad.dts | 16 ++++ tools/dtoc/dtoc_test_phandle_bad2.dts | 22 +++++ tools/dtoc/dtoc_test_phandle_reorder.dts | 23 +++++ tools/dtoc/dtoc_test_phandle_single.dts | 23 +++++ tools/dtoc/test_dtoc.py | 141 +++++++++++++++++++++++++++++++ 10 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 tools/dtoc/dtoc_test_add_prop.dts create mode 100644 tools/dtoc/dtoc_test_bad_reg.dts create mode 100644 tools/dtoc/dtoc_test_bad_reg2.dts create mode 100644 tools/dtoc/dtoc_test_phandle_bad.dts create mode 100644 tools/dtoc/dtoc_test_phandle_bad2.dts create mode 100644 tools/dtoc/dtoc_test_phandle_reorder.dts create mode 100644 tools/dtoc/dtoc_test_phandle_single.dts diff --git a/tools/dtoc/dtoc_test_add_prop.dts b/tools/dtoc/dtoc_test_add_prop.dts new file mode 100644 index 0000000..fa296e5 --- /dev/null +++ b/tools/dtoc/dtoc_test_add_prop.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + spl-test { + u-boot,dm-pre-reloc; + compatible = "sandbox,spl-test"; + intval = <1>; + }; + + spl-test2 { + u-boot,dm-pre-reloc; + compatible = "sandbox,spl-test"; + intarray = <5>; + }; +}; diff --git a/tools/dtoc/dtoc_test_addr32_64.dts b/tools/dtoc/dtoc_test_addr32_64.dts index 7891ee5..7599d5b 100644 --- a/tools/dtoc/dtoc_test_addr32_64.dts +++ b/tools/dtoc/dtoc_test_addr32_64.dts @@ -5,7 +5,7 @@ * Copyright 2017 Google, Inc */ - /dts-v1/; +/dts-v1/; / { #address-cells = <1>; diff --git a/tools/dtoc/dtoc_test_addr64_32.dts b/tools/dtoc/dtoc_test_addr64_32.dts index 759a7e8..85e4f5f 100644 --- a/tools/dtoc/dtoc_test_addr64_32.dts +++ b/tools/dtoc/dtoc_test_addr64_32.dts @@ -5,7 +5,7 @@ * Copyright 2017 Google, Inc */ - /dts-v1/; +/dts-v1/; / { #address-cells = <2>; diff --git a/tools/dtoc/dtoc_test_bad_reg.dts b/tools/dtoc/dtoc_test_bad_reg.dts new file mode 100644 index 0000000..1312acb --- /dev/null +++ b/tools/dtoc/dtoc_test_bad_reg.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + spl-test { + compatible = "test"; + reg = "fre"; + }; +}; diff --git a/tools/dtoc/dtoc_test_bad_reg2.dts b/tools/dtoc/dtoc_test_bad_reg2.dts new file mode 100644 index 0000000..3e9efa4 --- /dev/null +++ b/tools/dtoc/dtoc_test_bad_reg2.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + spl-test { + compatible = "test"; + reg = <1 2 3>; + }; +}; diff --git a/tools/dtoc/dtoc_test_phandle_bad.dts b/tools/dtoc/dtoc_test_phandle_bad.dts new file mode 100644 index 0000000..a3ddc59 --- /dev/null +++ b/tools/dtoc/dtoc_test_phandle_bad.dts @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + phandle-source { + u-boot,dm-pre-reloc; + compatible = "source"; + clocks = <20>; /* Invalid phandle */ + }; +}; diff --git a/tools/dtoc/dtoc_test_phandle_bad2.dts b/tools/dtoc/dtoc_test_phandle_bad2.dts new file mode 100644 index 0000000..fe25f56 --- /dev/null +++ b/tools/dtoc/dtoc_test_phandle_bad2.dts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + phandle: phandle-target { + u-boot,dm-pre-reloc; + compatible = "target"; + intval = <0>; + }; + + phandle-source2 { + u-boot,dm-pre-reloc; + compatible = "source"; + clocks = <&phandle>; + }; +}; diff --git a/tools/dtoc/dtoc_test_phandle_reorder.dts b/tools/dtoc/dtoc_test_phandle_reorder.dts new file mode 100644 index 0000000..aa71d56 --- /dev/null +++ b/tools/dtoc/dtoc_test_phandle_reorder.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + + phandle-source2 { + u-boot,dm-pre-reloc; + compatible = "source"; + clocks = <&phandle>; + }; + + phandle: phandle-target { + u-boot,dm-pre-reloc; + compatible = "target"; + #clock-cells = <0>; + }; +}; diff --git a/tools/dtoc/dtoc_test_phandle_single.dts b/tools/dtoc/dtoc_test_phandle_single.dts new file mode 100644 index 0000000..aacd0b1 --- /dev/null +++ b/tools/dtoc/dtoc_test_phandle_single.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test device tree file for dtoc + * + * Copyright 2018 Google, Inc + */ + +/dts-v1/; + +/ { + phandle: phandle-target { + u-boot,dm-pre-reloc; + compatible = "target"; + intval = <0>; + #clock-cells = <0>; + }; + + phandle-source2 { + u-boot,dm-pre-reloc; + compatible = "source"; + clocks = <&phandle>; + }; +}; diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index 11cac3f..622b39d 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -20,6 +20,7 @@ from dtb_platdata import get_value from dtb_platdata import tab_to import fdt import fdt_util +import test_util import tools our_path = os.path.dirname(os.path.realpath(__file__)) @@ -335,6 +336,68 @@ U_BOOT_DEVICE(phandle_source2) = { ''', data) + def test_phandle_single(self): + """Test output from a node containing a phandle reference""" + dtb_file = get_dtb_file('dtoc_test_phandle_single.dts') + output = tools.GetOutputFilename('output') + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + with open(output) as infile: + data = infile.read() + self._CheckStrings(HEADER + ''' +struct dtd_source { +\tstruct phandle_0_arg clocks[1]; +}; +struct dtd_target { +\tfdt32_t\t\tintval; +}; +''', data) + + def test_phandle_reorder(self): + """Test that phandle targets are generated before their references""" + dtb_file = get_dtb_file('dtoc_test_phandle_reorder.dts') + output = tools.GetOutputFilename('output') + dtb_platdata.run_steps(['platdata'], dtb_file, False, output) + with open(output) as infile: + data = infile.read() + self._CheckStrings(C_HEADER + ''' +static struct dtd_target dtv_phandle_target = { +}; +U_BOOT_DEVICE(phandle_target) = { +\t.name\t\t= "target", +\t.platdata\t= &dtv_phandle_target, +\t.platdata_size\t= sizeof(dtv_phandle_target), +}; + +static struct dtd_source dtv_phandle_source2 = { +\t.clocks\t\t\t= { +\t\t\t{&dtv_phandle_target, {}},}, +}; +U_BOOT_DEVICE(phandle_source2) = { +\t.name\t\t= "source", +\t.platdata\t= &dtv_phandle_source2, +\t.platdata_size\t= sizeof(dtv_phandle_source2), +}; + +''', data) + + def test_phandle_bad(self): + """Test a node containing an invalid phandle fails""" + dtb_file = get_dtb_file('dtoc_test_phandle_bad.dts') + output = tools.GetOutputFilename('output') + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + self.assertIn("Cannot parse 'clocks' in node 'phandle-source'", + str(e.exception)) + + def test_phandle_bad2(self): + """Test a phandle target missing its #*-cells property""" + dtb_file = get_dtb_file('dtoc_test_phandle_bad2.dts') + output = tools.GetOutputFilename('output') + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + self.assertIn("Node 'phandle-target' has no '#clock-cells' property", + str(e.exception)) + def test_aliases(self): """Test output from a node with multiple compatible strings""" dtb_file = get_dtb_file('dtoc_test_aliases.dts') @@ -560,3 +623,81 @@ U_BOOT_DEVICE(test3) = { }; ''', data) + + def test_bad_reg(self): + """Test that a reg property with an invalid type generates an error""" + dtb_file = get_dtb_file('dtoc_test_bad_reg.dts') + output = tools.GetOutputFilename('output') + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + self.assertIn("Node 'spl-test' reg property is not an int", + str(e.exception)) + + def test_bad_reg2(self): + """Test that a reg property with an invalid cell count is detected""" + dtb_file = get_dtb_file('dtoc_test_bad_reg2.dts') + output = tools.GetOutputFilename('output') + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + self.assertIn("Node 'spl-test' reg property has 3 cells which is not a multiple of na + ns = 1 + 1)", + str(e.exception)) + + def test_add_prop(self): + """Test that a subequent node can add a new property to a struct""" + dtb_file = get_dtb_file('dtoc_test_add_prop.dts') + output = tools.GetOutputFilename('output') + dtb_platdata.run_steps(['struct'], dtb_file, False, output) + with open(output) as infile: + data = infile.read() + self._CheckStrings(HEADER + ''' +struct dtd_sandbox_spl_test { +\tfdt32_t\t\tintarray; +\tfdt32_t\t\tintval; +}; +''', data) + + dtb_platdata.run_steps(['platdata'], dtb_file, False, output) + with open(output) as infile: + data = infile.read() + self._CheckStrings(C_HEADER + ''' +static struct dtd_sandbox_spl_test dtv_spl_test = { +\t.intval\t\t\t= 0x1, +}; +U_BOOT_DEVICE(spl_test) = { +\t.name\t\t= "sandbox_spl_test", +\t.platdata\t= &dtv_spl_test, +\t.platdata_size\t= sizeof(dtv_spl_test), +}; + +static struct dtd_sandbox_spl_test dtv_spl_test2 = { +\t.intarray\t\t= 0x5, +}; +U_BOOT_DEVICE(spl_test2) = { +\t.name\t\t= "sandbox_spl_test", +\t.platdata\t= &dtv_spl_test2, +\t.platdata_size\t= sizeof(dtv_spl_test2), +}; + +''', data) + + def testStdout(self): + """Test output to stdout""" + dtb_file = get_dtb_file('dtoc_test_simple.dts') + with test_util.capture_sys_output() as (stdout, stderr): + dtb_platdata.run_steps(['struct'], dtb_file, False, '-') + + def testNoCommand(self): + """Test running dtoc without a command""" + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps([], '', False, '') + self.assertIn("Please specify a command: struct, platdata", + str(e.exception)) + + def testBadCommand(self): + """Test running dtoc with an invalid command""" + dtb_file = get_dtb_file('dtoc_test_simple.dts') + output = tools.GetOutputFilename('output') + with self.assertRaises(ValueError) as e: + dtb_platdata.run_steps(['invalid-cmd'], dtb_file, False, output) + self.assertIn("Unknown command 'invalid-cmd': (use: struct, platdata)", + str(e.exception)) From ef6f77a8473fdc71c145a955ea9bdfd54de331d2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:36 -0600 Subject: [PATCH 34/40] test: Enable cover-coverage tests for dtoc and fdt Now that we have 100% code coverage we can enable these tests in the test script also. Signed-off-by: Simon Glass --- test/run | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/run b/test/run index 0b9188e..d77a1c3 100755 --- a/test/run +++ b/test/run @@ -29,6 +29,10 @@ PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test ./tools/dtoc/dtoc -t # $ sudo apt-get install python-pytest python-coverage PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test \ ./tools/binman/binman -T +PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test \ + ./tools/dtoc/dtoc -T +PYTHONPATH=${DTC_DIR}/pylibfdt DTC=${DTC_DIR}/dtc run_test \ + ./tools/dtoc/test_fdt -T if [ $result == 0 ]; then echo "Tests passed!" From fe57c784ad7187318a3aa6923d82095555bb50be Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:37 -0600 Subject: [PATCH 35/40] dtoc: Avoid unwanted output during tests At present some warnings are printed to indicate failures which are a known part of running the tests. Suppress these. Signed-off-by: Simon Glass --- tools/dtoc/fdt_util.py | 4 ++-- tools/dtoc/test_dtoc.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py index 88fc318..5b63141 100644 --- a/tools/dtoc/fdt_util.py +++ b/tools/dtoc/fdt_util.py @@ -51,7 +51,7 @@ def fdt_cells_to_cpu(val, cells): out = out << 32 | fdt32_to_cpu(val[1]) return out -def EnsureCompiled(fname): +def EnsureCompiled(fname, capture_stderr=False): """Compile an fdt .dts source file into a .dtb binary blob if needed. Args: @@ -86,7 +86,7 @@ def EnsureCompiled(fname): args.extend(search_list) args.append(dts_input) dtc = os.environ.get('DTC') or 'dtc' - command.Run(dtc, *args) + command.Run(dtc, *args, capture_stderr=capture_stderr) return dtb_output def GetInt(node, propname, default=None): diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index 622b39d..72bcb37 100644 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -47,16 +47,19 @@ C_HEADER = '''/* ''' -def get_dtb_file(dts_fname): + +def get_dtb_file(dts_fname, capture_stderr=False): """Compile a .dts file to a .dtb Args: dts_fname: Filename of .dts file in the current directory + capture_stderr: True to capture and discard stderr output Returns: Filename of compiled file in output directory """ - return fdt_util.EnsureCompiled(os.path.join(our_path, dts_fname)) + return fdt_util.EnsureCompiled(os.path.join(our_path, dts_fname), + capture_stderr=capture_stderr) class TestDtoc(unittest.TestCase): @@ -626,7 +629,8 @@ U_BOOT_DEVICE(test3) = { def test_bad_reg(self): """Test that a reg property with an invalid type generates an error""" - dtb_file = get_dtb_file('dtoc_test_bad_reg.dts') + # Capture stderr since dtc will emit warnings for this file + dtb_file = get_dtb_file('dtoc_test_bad_reg.dts', capture_stderr=True) output = tools.GetOutputFilename('output') with self.assertRaises(ValueError) as e: dtb_platdata.run_steps(['struct'], dtb_file, False, output) @@ -635,7 +639,8 @@ U_BOOT_DEVICE(test3) = { def test_bad_reg2(self): """Test that a reg property with an invalid cell count is detected""" - dtb_file = get_dtb_file('dtoc_test_bad_reg2.dts') + # Capture stderr since dtc will emit warnings for this file + dtb_file = get_dtb_file('dtoc_test_bad_reg2.dts', capture_stderr=True) output = tools.GetOutputFilename('output') with self.assertRaises(ValueError) as e: dtb_platdata.run_steps(['struct'], dtb_file, False, output) From 116adecb5e8e343cfc73a55f25a5d3d8463f4512 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:38 -0600 Subject: [PATCH 36/40] dtoc: Add functions to add integer properties Add a few simple functions to add a placeholder integer property, and set its value. Signed-off-by: Simon Glass --- tools/dtoc/fdt.py | 27 +++++++++++++++++++++++++++ tools/dtoc/test_fdt.py | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index e7703c1..9d69b42 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -270,6 +270,33 @@ class Node: del self.props[prop_name] self._fdt.Invalidate() + def AddZeroProp(self, prop_name): + """Add a new property to the device tree with an integer value of 0. + + Args: + prop_name: Name of property + """ + fdt_obj = self._fdt._fdt_obj + if fdt_obj.setprop_u32(self.Offset(), prop_name, 0, + (libfdt.NOSPACE,)) == -libfdt.NOSPACE: + fdt_obj.open_into(fdt_obj.totalsize() + 1024) + fdt_obj.setprop_u32(self.Offset(), prop_name, 0) + self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4) + self._fdt.Invalidate() + + def SetInt(self, prop_name, val): + """Update an integer property int the device tree. + + This is not allowed to change the size of the FDT. + + Args: + prop_name: Name of property + val: Value to set + """ + fdt_obj = self._fdt._fdt_obj + fdt_obj.setprop_u32(self.Offset(), prop_name, val) + + class Fdt: """Provides simple access to a flat device tree blob using libfdts. diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 49d188b..f085b1d 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -319,6 +319,26 @@ class TestProp(unittest.TestCase): self.assertTrue(isinstance(prop.value, list)) self.assertEqual(3, len(prop.value)) + def testAdd(self): + """Test adding properties""" + self.fdt.pack() + # This function should automatically expand the device tree + self.node.AddZeroProp('one') + self.node.AddZeroProp('two') + self.node.AddZeroProp('three') + + # Updating existing properties should be OK, since the device-tree size + # does not change + self.fdt.pack() + self.node.SetInt('one', 1) + self.node.SetInt('two', 2) + self.node.SetInt('three', 3) + + # This should fail since it would need to increase the device-tree size + with self.assertRaises(libfdt.FdtException) as e: + self.node.SetInt('four', 4) + self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) + class TestFdtUtil(unittest.TestCase): """Tests for the fdt_util module From 0a4357c4c2b29d787d70775dd6ad64b50e23082a Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:39 -0600 Subject: [PATCH 37/40] binman: Complete documentation of stages At present one of the stages is badly numbered and not described. Fix this. Signed-off-by: Simon Glass --- tools/binman/README | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/binman/README b/tools/binman/README index f74e392..3cfcf84 100644 --- a/tools/binman/README +++ b/tools/binman/README @@ -495,9 +495,11 @@ 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 position and size of entries should not be adjusted. -6. WriteEntryInfo() +7. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. +See 'Access to binman entry positions at run time' below for a description of +what happens in this stage. -7. BuildImage() - builds the image and writes it to a file. This is the final +8. BuildImage() - builds the image and writes it to a file. This is the final step. From ecab89737a4eb58c043388b1ca1c0f1dfdaa3346 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:40 -0600 Subject: [PATCH 38/40] binman: Add a ProcessFdt() method Some entry types modify the device tree, e.g. to remove microcode or add a property. So far this just modifies their local copy and does not affect a 'shared' device tree. Rather than doing this modification in the ObtainContents() method, and a new ProcessFdt() method which is specifically designed to modify this shared device tree. Move the existing device-tree code over to use this method, reducing ObtainContents() to the goal of just obtaining the contents without any processing, even for device tree. Signed-off-by: Simon Glass --- tools/binman/README | 23 ++++++++----- tools/binman/bsection.py | 15 +++++++++ tools/binman/control.py | 52 ++++++++++++++++++++++++++++- tools/binman/entry.py | 3 ++ tools/binman/etype/section.py | 3 ++ tools/binman/etype/u_boot_dtb_with_ucode.py | 46 ++++++++++++------------- tools/binman/etype/u_boot_ucode.py | 2 +- tools/binman/etype/u_boot_with_ucode_ptr.py | 7 ++-- tools/binman/image.py | 3 ++ 9 files changed, 117 insertions(+), 37 deletions(-) diff --git a/tools/binman/README b/tools/binman/README index 3cfcf84..008d575 100644 --- a/tools/binman/README +++ b/tools/binman/README @@ -462,7 +462,14 @@ Order of image creation Image creation proceeds in the following order, for each entry in the image. -1. GetEntryContents() - the contents of each entry are obtained, normally by +1. ProcessFdt() - process the device tree information as required by the +particular entry. This may involve adding or deleting properties. If the +processing is complete, this method should return True. If the processing +cannot complete because it needs the ProcessFdt() method of another entry to +run first, this method should return False, in which case it will be called +again later. + +2. GetEntryContents() - the contents of each entry are obtained, normally by reading from a file. This calls the Entry.ObtainContents() to read the contents. The default version of Entry.ObtainContents() calls Entry.GetDefaultFilename() and then reads that file. So a common mechanism @@ -471,35 +478,35 @@ functions must return True when they have read the contents. Binman will retry calling the functions a few times if False is returned, allowing dependencies between the contents of different entries. -2. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can +3. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can return a dict containing entries that need updating. The key should be the entry name and the value is a tuple (pos, size). This allows an entry to provide the position and size for other entries. The default implementation of GetEntryPositions() returns {}. -3. PackEntries() - calls Entry.Pack() which figures out the position and +4. PackEntries() - calls Entry.Pack() which figures out the position and size of an entry. The 'current' image position is passed in, and the function returns the position immediately after the entry being packed. The default implementation of Pack() is usually sufficient. -4. CheckSize() - checks that the contents of all the entries fits within +5. CheckSize() - checks that the contents of all the entries fits within the image size. If the image does not have a defined size, the size is set large enough to hold all the entries. -5. CheckEntries() - checks that the entries do not overlap, nor extend +6. CheckEntries() - checks that the entries do not overlap, nor extend outside the image. -6. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry. +7. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry. 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 position and size of entries should not be adjusted. -7. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. +8. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. See 'Access to binman entry positions at run time' below for a description of what happens in this stage. -8. BuildImage() - builds the image and writes it to a file. This is the final +9. BuildImage() - builds the image and writes it to a file. This is the final step. diff --git a/tools/binman/bsection.py b/tools/binman/bsection.py index 06a6711..3ed361d 100644 --- a/tools/binman/bsection.py +++ b/tools/binman/bsection.py @@ -90,6 +90,21 @@ class Section(object): entry.SetPrefix(self._name_prefix) self._entries[node.name] = entry + 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 diff --git a/tools/binman/control.py b/tools/binman/control.py index 9243472..5325a6a 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -21,6 +21,11 @@ import tout # Make this global so that it can be referenced from tests images = OrderedDict() +# Records the device-tree files known to binman, keyed by filename (e.g. +# 'u-boot-spl.dtb') +fdt_files = {} + + def _ReadImageDesc(binman_node): """Read the image descriptions from the /binman node @@ -53,6 +58,24 @@ def _FindBinmanNode(dtb): return node return None +def GetFdt(fname): + """Get the Fdt object for a particular device-tree filename + + Binman keeps track of at least one device-tree file called u-boot.dtb but + can also have others (e.g. for SPL). This function looks up the given + filename and returns the associated Fdt object. + + Args: + fname: Filename to look up (e.g. 'u-boot.dtb'). + + Returns: + Fdt object associated with the filename + """ + return fdt_files[fname] + +def GetFdtPath(fname): + return fdt_files[fname]._fname + def Binman(options, args): """The main control code for binman @@ -93,12 +116,39 @@ def Binman(options, args): try: tools.SetInputDirs(options.indir) tools.PrepareOutputDir(options.outdir, options.preserve) - dtb = fdt.FdtScan(dtb_fname) + + # 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 + # in binman. + dtb_fname = fdt_util.EnsureCompiled(dtb_fname) + fname = tools.GetOutputFilename('u-boot-out.dtb') + with open(dtb_fname) as infd: + with open(fname, 'wb') as outfd: + outfd.write(infd.read()) + dtb = fdt.FdtScan(fname) + + # Note the file so that GetFdt() can find it + fdt_files['u-boot.dtb'] = dtb node = _FindBinmanNode(dtb) if not node: raise ValueError("Device tree '%s' does not have a 'binman' " "node" % dtb_fname) + images = _ReadImageDesc(node) + + # Prepare the device tree by making sure that any missing + # properties are added (e.g. 'pos' and 'size'). The values of these + # may not be correct yet, but we add placeholders so that the + # size of the device tree is correct. Later, in + # SetCalculatedProperties() we will insert the correct values + # without changing the device-tree size, thus ensuring that our + # entry positions remain the same. + for image in images.values(): + image.ProcessFdt(dtb) + + dtb.Pack() + dtb.Flush() + for image in images.values(): # Perform all steps for this image, including checking and # writing it. This means that errors found with a later diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 303c992..62e65c9 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -130,6 +130,9 @@ class Entry(object): self.align_end = fdt_util.GetInt(self._node, 'align-end') self.pos_unset = fdt_util.GetBool(self._node, 'pos-unset') + def ProcessFdt(self, fdt): + return True + def SetPrefix(self, prefix): """Set the name prefix for a node diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 36b31a8..9b38738 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -17,6 +17,9 @@ class Entry_section(Entry): Entry.__init__(self, image, etype, node) self._section = bsection.Section(node.name, node) + def ProcessFdt(self, fdt): + return self._section.ProcessFdt(fdt) + def ObtainContents(self): return self._section.GetEntryContents() diff --git a/tools/binman/etype/u_boot_dtb_with_ucode.py b/tools/binman/etype/u_boot_dtb_with_ucode.py index 1e530d6..3808d6d 100644 --- a/tools/binman/etype/u_boot_dtb_with_ucode.py +++ b/tools/binman/etype/u_boot_dtb_with_ucode.py @@ -5,6 +5,7 @@ # Entry-type module for U-Boot device tree with the microcode removed # +import control import fdt from entry import Entry from blob import Entry_blob @@ -22,13 +23,13 @@ class Entry_u_boot_dtb_with_ucode(Entry_blob): self.collate = False self.ucode_offset = None self.ucode_size = None + self.ucode = None + self.ready = False def GetDefaultFilename(self): return 'u-boot.dtb' - def ObtainContents(self): - Entry_blob.ObtainContents(self) - + def ProcessFdt(self, fdt): # If the section does not need microcode, there is nothing to do ucode_dest_entry = self.section.FindEntryType( 'u-boot-spl-with-ucode-ptr') @@ -38,36 +39,35 @@ class Entry_u_boot_dtb_with_ucode(Entry_blob): if not ucode_dest_entry or not ucode_dest_entry.target_pos: return True - # Create a new file to hold the copied device tree - dtb_name = 'u-boot-dtb-with-ucode.dtb' - fname = tools.GetOutputFilename(dtb_name) - with open(fname, 'wb') as fd: - fd.write(self.data) - # Remove the microcode - dtb = fdt.FdtScan(fname) - ucode = dtb.GetNode('/microcode') - if not ucode: + fname = self.GetDefaultFilename() + fdt = control.GetFdt(fname) + self.ucode = fdt.GetNode('/microcode') + if not self.ucode: raise self.Raise("No /microcode node found in '%s'" % fname) # There's no need to collate it (move all microcode into one place) # if we only have one chunk of microcode. - self.collate = len(ucode.subnodes) > 1 - for node in ucode.subnodes: + self.collate = len(self.ucode.subnodes) > 1 + for node in self.ucode.subnodes: data_prop = node.props.get('data') if data_prop: self.ucode_data += ''.join(data_prop.bytes) if self.collate: - prop = node.DeleteProp('data') - else: + node.DeleteProp('data') + return True + + def ObtainContents(self): + # Call the base class just in case it does something important. + Entry_blob.ObtainContents(self) + self._pathname = control.GetFdtPath(self._filename) + self.ReadContents() + if self.ucode: + for node in self.ucode.subnodes: + data_prop = node.props.get('data') + if data_prop and not self.collate: # Find the offset in the device tree of the ucode data self.ucode_offset = data_prop.GetOffset() + 12 self.ucode_size = len(data_prop.bytes) - if self.collate: - dtb.Pack() - dtb.Flush() - - # Make this file the contents of this entry - self._pathname = fname - self.ReadContents() + self.ready = True return True diff --git a/tools/binman/etype/u_boot_ucode.py b/tools/binman/etype/u_boot_ucode.py index 8cae7de..ea0c85c 100644 --- a/tools/binman/etype/u_boot_ucode.py +++ b/tools/binman/etype/u_boot_ucode.py @@ -71,7 +71,7 @@ class Entry_u_boot_ucode(Entry_blob): fdt_entry = self.section.FindEntryType('u-boot-dtb-with-ucode') if not fdt_entry: return True - if not fdt_entry.ucode_data: + if not fdt_entry.ready: return False if not fdt_entry.collate: diff --git a/tools/binman/etype/u_boot_with_ucode_ptr.py b/tools/binman/etype/u_boot_with_ucode_ptr.py index 86945f3..8b1e411 100644 --- a/tools/binman/etype/u_boot_with_ucode_ptr.py +++ b/tools/binman/etype/u_boot_with_ucode_ptr.py @@ -28,7 +28,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob): def GetDefaultFilename(self): return 'u-boot-nodtb.bin' - def ObtainContents(self): + def ProcessFdt(self, fdt): # Figure out where to put the microcode pointer fname = tools.GetInputFilename(self.elf_fname) sym = elf.GetSymbolAddress(fname, '_dt_ucode_base_size') @@ -36,8 +36,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob): self.target_pos = sym elif not fdt_util.GetBool(self._node, 'optional-ucode'): self.Raise('Cannot locate _dt_ucode_base_size symbol in u-boot') - - return Entry_blob.ObtainContents(self) + return True def ProcessContents(self): # If the image does not need microcode, there is nothing to do @@ -73,7 +72,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob): pos, size = ucode_entry.pos, ucode_entry.size else: dtb_entry = self.section.FindEntryType('u-boot-dtb-with-ucode') - if not dtb_entry: + if not dtb_entry or not dtb_entry.ready: self.Raise('Cannot find microcode region u-boot-dtb-with-ucode') pos = dtb_entry.pos + dtb_entry.ucode_offset size = dtb_entry.ucode_size diff --git a/tools/binman/image.py b/tools/binman/image.py index 835b66c..9732493 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -54,6 +54,9 @@ class Image: self._filename = filename self._section = bsection.Section('main-section', self._node) + def ProcessFdt(self, fdt): + return self._section.ProcessFdt(fdt) + def GetEntryContents(self): """Call ObtainContents() for the section """ From 078ab1a2f5a9901f5f9983163d4f98b8d94bfda0 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:41 -0600 Subject: [PATCH 39/40] binman: Add a SetCalculatedProperties() method Once binman has packed the image, the position and size of each entry is known. It is then possible for binman to update the device tree with these positions. Since placeholder values have been added, this does not affect the size of the device tree and therefore the packing does not need to be performed again. Add a new SetCalculatedProperties method to handle this. Signed-off-by: Simon Glass --- tools/binman/README | 29 ++++++++++++++++++++--------- tools/binman/bsection.py | 8 ++++++++ tools/binman/cmdline.py | 2 ++ tools/binman/control.py | 4 ++++ tools/binman/entry.py | 11 +++++++++++ tools/binman/etype/section.py | 8 ++++++++ tools/binman/image.py | 14 ++++++++++++++ 7 files changed, 67 insertions(+), 9 deletions(-) diff --git a/tools/binman/README b/tools/binman/README index 008d575..8b598a7 100644 --- a/tools/binman/README +++ b/tools/binman/README @@ -462,14 +462,22 @@ Order of image creation Image creation proceeds in the following order, for each entry in the image. -1. ProcessFdt() - process the device tree information as required by the +1. AddMissingProperties() - binman can add calculated values to the device +tree as part of its processing, for example the position and size of each +entry. This method adds any properties associated with this, expanding the +device tree as needed. These properties can have placeholder values which are +set later by SetCalculatedProperties(). By that stage the size of sections +cannot be changed (since it would cause the images to need to be repacked), +but the correct values can be inserted. + +2. ProcessFdt() - process the device tree information as required by the particular entry. This may involve adding or deleting properties. If the processing is complete, this method should return True. If the processing cannot complete because it needs the ProcessFdt() method of another entry to run first, this method should return False, in which case it will be called again later. -2. GetEntryContents() - the contents of each entry are obtained, normally by +3. GetEntryContents() - the contents of each entry are obtained, normally by reading from a file. This calls the Entry.ObtainContents() to read the contents. The default version of Entry.ObtainContents() calls Entry.GetDefaultFilename() and then reads that file. So a common mechanism @@ -478,35 +486,38 @@ functions must return True when they have read the contents. Binman will retry calling the functions a few times if False is returned, allowing dependencies between the contents of different entries. -3. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can +4. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can return a dict containing entries that need updating. The key should be the entry name and the value is a tuple (pos, size). This allows an entry to provide the position and size for other entries. The default implementation of GetEntryPositions() returns {}. -4. PackEntries() - calls Entry.Pack() which figures out the position and +5. PackEntries() - calls Entry.Pack() which figures out the position and size of an entry. The 'current' image position is passed in, and the function returns the position immediately after the entry being packed. The default implementation of Pack() is usually sufficient. -5. CheckSize() - checks that the contents of all the entries fits within +6. CheckSize() - checks that the contents of all the entries fits within the image size. If the image does not have a defined size, the size is set large enough to hold all the entries. -6. CheckEntries() - checks that the entries do not overlap, nor extend +7. CheckEntries() - checks that the entries do not overlap, nor extend outside the image. -7. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry. +8. SetCalculatedProperties() - update any calculated properties in the device +tree. This sets the correct 'pos' and 'size' vaues, for example. + +9. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry. 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 position and size of entries should not be adjusted. -8. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. +10. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. See 'Access to binman entry positions at run time' below for a description of what happens in this stage. -9. BuildImage() - builds the image and writes it to a file. This is the final +11. BuildImage() - builds the image and writes it to a file. This is the final step. diff --git a/tools/binman/bsection.py b/tools/binman/bsection.py index 3ed361d..de439ef 100644 --- a/tools/binman/bsection.py +++ b/tools/binman/bsection.py @@ -90,6 +90,14 @@ class Section(object): entry.SetPrefix(self._name_prefix) self._entries[node.name] = entry + def AddMissingProperties(self): + for entry in self._entries.values(): + entry.AddMissingProperties() + + def SetCalculatedProperties(self): + for entry in self._entries.values(): + entry.SetCalculatedProperties() + def ProcessFdt(self, fdt): todo = self._entries.values() for passnum in range(3): diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py index bf63919..ae2d167 100644 --- a/tools/binman/cmdline.py +++ b/tools/binman/cmdline.py @@ -42,6 +42,8 @@ def ParseArgs(argv): 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', + default=False, help='Update the binman node with position/size info') parser.add_option('-v', '--verbosity', default=1, type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, ' '4=debug') diff --git a/tools/binman/control.py b/tools/binman/control.py index 5325a6a..eafabf0 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -144,6 +144,8 @@ def Binman(options, args): # without changing the device-tree size, thus ensuring that our # entry positions remain the same. for image in images.values(): + if options.update_fdt: + image.AddMissingProperties() image.ProcessFdt(dtb) dtb.Pack() @@ -159,6 +161,8 @@ def Binman(options, args): image.PackEntries() image.CheckSize() image.CheckEntries() + if options.update_fdt: + image.SetCalculatedProperties() image.ProcessEntryContents() image.WriteSymbols() image.BuildImage() diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 62e65c9..6a173e6 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -130,6 +130,17 @@ class Entry(object): self.align_end = fdt_util.GetInt(self._node, 'align-end') self.pos_unset = fdt_util.GetBool(self._node, 'pos-unset') + def AddMissingProperties(self): + """Add new properties to the device tree as needed for this entry""" + for prop in ['pos', 'size']: + if not prop in self._node.props: + self._node.AddZeroProp(prop) + + def SetCalculatedProperties(self): + """Set the value of device-tree properties calculated by binman""" + self._node.SetInt('pos', self.pos) + self._node.SetInt('size', self.size) + def ProcessFdt(self, fdt): return True diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 9b38738..787257d 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -20,6 +20,10 @@ class Entry_section(Entry): def ProcessFdt(self, fdt): return self._section.ProcessFdt(fdt) + def AddMissingProperties(self): + Entry.AddMissingProperties(self) + self._section.AddMissingProperties() + def ObtainContents(self): return self._section.GetEntryContents() @@ -45,6 +49,10 @@ class Entry_section(Entry): """Write symbol values into binary files for access at run time""" self._section.WriteSymbols() + def SetCalculatedProperties(self): + Entry.SetCalculatedProperties(self) + self._section.SetCalculatedProperties() + def ProcessContents(self): self._section.ProcessEntryContents() super(Entry_section, self).ProcessContents() diff --git a/tools/binman/image.py b/tools/binman/image.py index 9732493..94028f1 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -54,6 +54,17 @@ class Image: self._filename = filename self._section = bsection.Section('main-section', self._node) + def AddMissingProperties(self): + """Add properties that are not present in the device tree + + When binman has completed packing the entries the position 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. + """ + self._section.AddMissingProperties() + def ProcessFdt(self, fdt): return self._section.ProcessFdt(fdt) @@ -82,6 +93,9 @@ class Image: """Check that entries do not overlap or extend outside the image""" self._section.CheckEntries() + def SetCalculatedProperties(self): + self._section.SetCalculatedProperties() + def ProcessEntryContents(self): """Call the ProcessContents() method for each entry From 16b8d6b76992690c65c58dc8b0591496cc5e46ef Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jul 2018 10:27:42 -0600 Subject: [PATCH 40/40] binman: Support updating the device tree with calc'd info It is useful to write the position and size of each entry back to the device tree so that U-Boot can access this at runtime. Add a feature to support this, along with associated tests. Signed-off-by: Simon Glass --- tools/binman/README | 6 +-- tools/binman/control.py | 2 + tools/binman/etype/_testing.py | 10 +++++ tools/binman/ftest.py | 77 ++++++++++++++++++++++++++++----- tools/binman/test/60_fdt_update.dts | 31 +++++++++++++ tools/binman/test/61_fdt_update_bad.dts | 32 ++++++++++++++ 6 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 tools/binman/test/60_fdt_update.dts create mode 100644 tools/binman/test/61_fdt_update_bad.dts diff --git a/tools/binman/README b/tools/binman/README index 8b598a7..207928a 100644 --- a/tools/binman/README +++ b/tools/binman/README @@ -669,13 +669,11 @@ To do ----- Some ideas: -- Fill out the device tree to include the final position and size of each - entry (since the input file may not always specify these). See also - 'Access to binman entry positions at run time' above - 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) +- 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 diff --git a/tools/binman/control.py b/tools/binman/control.py index eafabf0..a40b300 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -168,6 +168,8 @@ def Binman(options, args): image.BuildImage() if options.map: image.WriteMap() + with open(fname, 'wb') as outfd: + outfd.write(dtb.GetContents()) finally: tools.FinaliseOutputDir() finally: diff --git a/tools/binman/etype/_testing.py b/tools/binman/etype/_testing.py index 04bdc6c..6a1af57 100644 --- a/tools/binman/etype/_testing.py +++ b/tools/binman/etype/_testing.py @@ -24,6 +24,9 @@ class Entry__testing(Entry): 'return-unknown-contents') self.bad_update_contents = fdt_util.GetBool(self._node, 'bad-update-contents') + self.process_fdt_ready = False + self.never_complete_process_fdt = fdt_util.GetBool(self._node, + 'never-complete-process-fdt') def ObtainContents(self): if self.return_unknown_contents: @@ -42,3 +45,10 @@ class Entry__testing(Entry): # Request to update the conents with something larger, to cause a # failure. self.ProcessContentsUpdate('aa') + + def ProcessFdt(self, fdt): + """Force reprocessing the first time""" + ready = self.process_fdt_ready + if not self.never_complete_process_fdt: + self.process_fdt_ready = True + return ready diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index af3b4dc..12164a8 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -146,19 +146,23 @@ class TestFunctional(unittest.TestCase): # options.verbosity = tout.DEBUG return control.Binman(options, args) - def _DoTestFile(self, fname, debug=False, map=False): + def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False): """Run binman with a given test file Args: fname: Device-tree source filename to use (e.g. 05_simple.dts) debug: True to enable debugging output map: True to output map files for the images + update_dtb: Update the position and size of each entry in the device + tree before packing it into the image """ args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)] if debug: args.append('-D') if map: args.append('-m') + if update_dtb: + args.append('-up') return self._DoBinman(*args) def _SetupDtb(self, fname, outfile='u-boot.dtb'): @@ -183,7 +187,8 @@ class TestFunctional(unittest.TestCase): TestFunctional._MakeInputFile(outfile, data) return data - def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False): + def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False, + update_dtb=False): """Run binman and return the resulting image This runs binman with a given test file and then reads the resulting @@ -199,6 +204,8 @@ class TestFunctional(unittest.TestCase): test contents (the U_BOOT_DTB_DATA string) can be used. But in some test we need the real contents. map: True to output map files for the images + update_dtb: Update the position and size of each entry in the device + tree before packing it into the image Returns: Tuple: @@ -212,21 +219,22 @@ class TestFunctional(unittest.TestCase): dtb_data = self._SetupDtb(fname) try: - retcode = self._DoTestFile(fname, map=map) + retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb) self.assertEqual(0, retcode) + out_dtb_fname = control.GetFdtPath('u-boot.dtb') # Find the (only) image, read it and return its contents image = control.images['image'] - fname = tools.GetOutputFilename('image.bin') - self.assertTrue(os.path.exists(fname)) + image_fname = tools.GetOutputFilename('image.bin') + self.assertTrue(os.path.exists(image_fname)) if map: map_fname = tools.GetOutputFilename('image.map') with open(map_fname) as fd: map_data = fd.read() else: map_data = None - with open(fname) as fd: - return fd.read(), dtb_data, map_data + with open(image_fname) as fd: + return fd.read(), dtb_data, map_data, out_dtb_fname finally: # Put the test file back if use_real_dtb: @@ -300,6 +308,26 @@ class TestFunctional(unittest.TestCase): """ return struct.unpack('>L', dtb[4:8])[0] + def _GetPropTree(self, dtb_data, node_names): + def AddNode(node, path): + if node.name != '/': + path += '/' + node.name + #print 'path', path + for subnode in node.subnodes: + for prop in subnode.props.values(): + if prop.name in node_names: + prop_path = path + '/' + subnode.name + ':' + prop.name + tree[prop_path[len('/binman/'):]] = fdt_util.fdt32_to_cpu( + prop.value) + #print ' ', prop.name + AddNode(subnode, path) + + tree = {} + dtb = fdt.Fdt(dtb_data) + dtb.Scan() + AddNode(dtb.GetRoot(), '') + return tree + def testRun(self): """Test a basic run with valid args""" result = self._RunBinman('-h') @@ -845,7 +873,7 @@ class TestFunctional(unittest.TestCase): """Test that we can cope with an image without microcode (e.g. qemu)""" with open(self.TestFile('u_boot_no_ucode_ptr')) as fd: TestFunctional._MakeInputFile('u-boot', fd.read()) - data, dtb, _ = self._DoReadFileDtb('44_x86_optional_ucode.dts', True) + data, dtb, _, _ = self._DoReadFileDtb('44_x86_optional_ucode.dts', True) # Now check the device tree has no microcode self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)]) @@ -980,7 +1008,7 @@ class TestFunctional(unittest.TestCase): def testMap(self): """Tests outputting a map of the images""" - _, _, map_data = self._DoReadFileDtb('55_sections.dts', map=True) + _, _, map_data, _ = self._DoReadFileDtb('55_sections.dts', map=True) self.assertEqual('''Position Size Name 00000000 00000010 section@0 00000000 00000004 u-boot @@ -990,7 +1018,7 @@ class TestFunctional(unittest.TestCase): def testNamePrefix(self): """Tests that name prefixes are used""" - _, _, map_data = self._DoReadFileDtb('56_name_prefix.dts', map=True) + _, _, map_data, _ = self._DoReadFileDtb('56_name_prefix.dts', map=True) self.assertEqual('''Position Size Name 00000000 00000010 section@0 00000000 00000004 ro-u-boot @@ -1013,6 +1041,35 @@ class TestFunctional(unittest.TestCase): self.assertIn("Node '/binman/_testing': Cannot update entry size from " '2 to 1', str(e.exception)) + def testUpdateFdt(self): + """Test that we can update the device tree with pos/size info""" + _, _, _, out_dtb_fname = self._DoReadFileDtb('60_fdt_update.dts', + update_dtb=True) + props = self._GetPropTree(out_dtb_fname, ['pos', 'size']) + with open('/tmp/x.dtb', 'wb') as outf: + with open(out_dtb_fname) as inf: + outf.write(inf.read()) + self.assertEqual({ + '_testing:pos': 32, + '_testing:size': 1, + 'section@0/u-boot:pos': 0, + 'section@0/u-boot:size': len(U_BOOT_DATA), + 'section@0:pos': 0, + 'section@0:size': 16, + + 'section@1/u-boot:pos': 0, + 'section@1/u-boot:size': len(U_BOOT_DATA), + 'section@1:pos': 16, + 'section@1:size': 16, + 'size': 40 + }, props) + + def testUpdateFdtBad(self): + """Test that we detect when ProcessFdt never completes""" + with self.assertRaises(ValueError) as e: + self._DoReadFileDtb('61_fdt_update_bad.dts', update_dtb=True) + self.assertIn('Could not complete processing of Fdt: remaining ' + '[<_testing.Entry__testing', str(e.exception)) if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/60_fdt_update.dts b/tools/binman/test/60_fdt_update.dts new file mode 100644 index 0000000..f53c8a5 --- /dev/null +++ b/tools/binman/test/60_fdt_update.dts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pad-byte = <0x26>; + size = <0x28>; + section@0 { + read-only; + name-prefix = "ro-"; + size = <0x10>; + pad-byte = <0x21>; + + u-boot { + }; + }; + section@1 { + name-prefix = "rw-"; + size = <0x10>; + pad-byte = <0x61>; + + u-boot { + }; + }; + _testing { + }; + }; +}; diff --git a/tools/binman/test/61_fdt_update_bad.dts b/tools/binman/test/61_fdt_update_bad.dts new file mode 100644 index 0000000..e5abf31 --- /dev/null +++ b/tools/binman/test/61_fdt_update_bad.dts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pad-byte = <0x26>; + size = <0x28>; + section@0 { + read-only; + name-prefix = "ro-"; + size = <0x10>; + pad-byte = <0x21>; + + u-boot { + }; + }; + section@1 { + name-prefix = "rw-"; + size = <0x10>; + pad-byte = <0x61>; + + u-boot { + }; + }; + _testing { + never-complete-process-fdt; + }; + }; +};