From 915458e148d969ccc2e6d246794324f1f34f5b6b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 30 Jul 2022 15:52:03 -0600 Subject: vbe: Add some documentation Add a few links to documents about Verified Boot for Embedded (VBE). These will be expanded as development proceeds. Signed-off-by: Simon Glass --- doc/develop/bootstd.rst | 1 + doc/develop/index.rst | 1 + doc/develop/vbe.rst | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 doc/develop/vbe.rst (limited to 'doc/develop') diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst index 5e9c0d282bb..dadd3473e5c 100644 --- a/doc/develop/bootstd.rst +++ b/doc/develop/bootstd.rst @@ -32,6 +32,7 @@ way to boot with U-Boot. The feature is extensible to different Operating Systems (such as Chromium OS) and devices (beyond just block and network devices). It supports EFI boot and EFI bootmgr too. +Finally, standard boot supports the operation of :doc:`vbe`. Bootflow -------- diff --git a/doc/develop/index.rst b/doc/develop/index.rst index 7c41e3f1b6e..c94c7fe0efa 100644 --- a/doc/develop/index.rst +++ b/doc/develop/index.rst @@ -39,6 +39,7 @@ Implementation smbios spl uefi/index + vbe version Debugging diff --git a/doc/develop/vbe.rst b/doc/develop/vbe.rst new file mode 100644 index 00000000000..8f147fd9360 --- /dev/null +++ b/doc/develop/vbe.rst @@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +Verified Boot for Embedded (VBE) +================================ + +Introduction +------------ + +VBE provides a standard boot mechanism for embedded systems. If defines +how firmware and Operating Systems are located, updated and verified. + +Within U-Boot, one or more VBE bootmeths implement the boot logic. For example, +the vbe-simple bootmeth handles finding the firmware (e.g. in MMC) and starting +it. Typically the bootmeth is started up in VPL and controls which SPL and +U-Boot binaries are loaded. + +A 'vbe' command provides access to various aspects of VBE's operation, including +listing methods and getting the status for a method. + +For a detailed overview of VBE, see vbe-intro_. A fuller description of +bootflows is at vbe-bootflows_ and the firmware-update mechanism is described at +vbe-fwupdate_. + +.. _vbe-intro: https://docs.google.com/document/d/e/2PACX-1vQjXLPWMIyVktaTMf8edHZYDrEvMYD_iNzIj1FgPmKF37fpglAC47Tt5cvPBC5fvTdoK-GA5Zv1wifo/pub +.. _vbe-bootflows: https://docs.google.com/document/d/e/2PACX-1vR0OzhuyRJQ8kdeOibS3xB1rVFy3J4M_QKTM5-3vPIBNcdvR0W8EXu9ymG-yWfqthzWoM4JUNhqwydN/pub +.. _vbe-fwupdate: https://docs.google.com/document/d/e/2PACX-1vTnlIL17vVbl6TVoTHWYMED0bme7oHHNk-g5VGxblbPiKIdGDALE1HKId8Go5f0g1eziLsv4h9bocbk/pub -- cgit v1.3.1 From 72b338aa2cd4afff8e92ab28199bc2db073cfea7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 30 Jul 2022 15:52:07 -0600 Subject: dm: core: Add a note about how livetree updates work The unflattening algorithm results in a single block of memory being allocated for the whole tree. When writing new properties, these are allocated new memory outside that block. When the block is freed, the allocated properties remain. Document how this works and the potential memory leak, as well as mentioning that updating the livetree is actually supported now. Signed-off-by: Simon Glass --- doc/develop/driver-model/livetree.rst | 17 +++++++++++++---- include/dm/ofnode.h | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'doc/develop') diff --git a/doc/develop/driver-model/livetree.rst b/doc/develop/driver-model/livetree.rst index 9f654f3b894..fb2969259d0 100644 --- a/doc/develop/driver-model/livetree.rst +++ b/doc/develop/driver-model/livetree.rst @@ -211,9 +211,18 @@ using it in new code. Modifying the livetree ---------------------- -This is not currently supported. Once implemented it should provide a much -more efficient implementation for modification of the device tree than using -the flat tree. +This is supported in a limited way, with ofnode_write_prop() and related +functions. + +The unflattening algorithm results in a single block of memory being +allocated for the whole tree. When writing new properties, these are +allocated new memory outside that block. When the block is freed, the +allocated properties remain. This can result in a memory leak. + +The solution to this leak would be to add a flag for properties (and nodes when +support is provided for adding those) that indicates that they should be +freed. Then the tree can be scanned for these 'separately allocated' nodes and +properties before freeing the memory block. Internal implementation @@ -281,6 +290,6 @@ Live tree support was introduced in U-Boot 2017.07. There is still quite a bit of work to do to flesh this out: - tests for all access functions -- support for livetree modification +- more support for livetree modification - addition of more access functions as needed - support for livetree in SPL and before relocation (if desired) diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 346b09c7d96..5a5309d79a7 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -1081,7 +1081,8 @@ int ofnode_device_is_compatible(ofnode node, const char *compat); * ofnode_write_prop() - Set a property of a ofnode * * Note that the value passed to the function is *not* allocated by the - * function itself, but must be allocated by the caller if necessary. + * function itself, but must be allocated by the caller if necessary. However + * it does allocate memory for the property struct and name. * * @node: The node for whose property should be set * @propname: The name of the property to set -- cgit v1.3.1 From 331048471dee5c1d9cede54382256e6cfaee2370 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 30 Jul 2022 15:52:08 -0600 Subject: dm: core: Introduce support for multiple trees At present ofnode only works with a single device tree, for the most part. This is the control FDT used by U-Boot. When booting an OS we may obtain a different device tree and want to modify it. Add some initial support for this into the ofnode API. Note that we don't permit aliases in this other device tree, since the of_access implementation maintains a list of aliases collected at start-up. Also, we don't need aliases to do fixups in the other FDT. So make sure that flat tree and live tree processing are consistent in this area. Signed-off-by: Simon Glass --- doc/develop/driver-model/livetree.rst | 17 +++++++++ drivers/core/of_access.c | 14 +++++--- drivers/core/ofnode.c | 11 ++++++ include/dm/of_access.h | 10 ++++-- include/dm/ofnode.h | 28 +++++++++++++++ include/dm/ofnode_decl.h | 13 +++++++ include/of_live.h | 16 +++++++++ lib/of_live.c | 14 +------- test/dm/ofnode.c | 67 +++++++++++++++++++++++++++++++++++ 9 files changed, 171 insertions(+), 19 deletions(-) (limited to 'doc/develop') diff --git a/doc/develop/driver-model/livetree.rst b/doc/develop/driver-model/livetree.rst index fb2969259d0..c29f29b205b 100644 --- a/doc/develop/driver-model/livetree.rst +++ b/doc/develop/driver-model/livetree.rst @@ -225,6 +225,23 @@ freed. Then the tree can be scanned for these 'separately allocated' nodes and properties before freeing the memory block. +Multiple livetrees +------------------ + +The livetree implementation was originally designed for use with the control +FDT. This means that the FDT fix-ups (ft_board_setup() and the like, must use +a flat tree. + +It would be helpful to use livetree for fixups, since adding a lot of nodes and +properties would involve less memory copying and be more efficient. As a step +towards this, an `oftree` type has been introduced. It is normally set to +oftree_default() but can be set to other values. Eventually this should allow +the use of FDT fixups using the ofnode interface, instead of the low-level +libfdt one. + +See dm_test_ofnode_root() for some examples. + + Internal implementation ----------------------- diff --git a/drivers/core/of_access.c b/drivers/core/of_access.c index c20b19cb50f..0e5915a43e6 100644 --- a/drivers/core/of_access.c +++ b/drivers/core/of_access.c @@ -343,24 +343,30 @@ static struct device_node *__of_find_node_by_path(struct device_node *parent, #define for_each_property_of_node(dn, pp) \ for (pp = dn->properties; pp != NULL; pp = pp->next) -struct device_node *of_find_node_opts_by_path(const char *path, +struct device_node *of_find_node_opts_by_path(struct device_node *root, + const char *path, const char **opts) { struct device_node *np = NULL; struct property *pp; const char *separator = strchr(path, ':'); + if (!root) + root = gd->of_root; if (opts) *opts = separator ? separator + 1 : NULL; if (strcmp(path, "/") == 0) - return of_node_get(gd->of_root); + return of_node_get(root); /* The path could begin with an alias */ if (*path != '/') { int len; const char *p = separator; + /* Only allow alias processing on the control FDT */ + if (root != gd->of_root) + return NULL; if (!p) p = strchrnul(path, '/'); len = p - path; @@ -383,7 +389,7 @@ struct device_node *of_find_node_opts_by_path(const char *path, /* Step down the tree matching path components */ if (!np) - np = of_node_get(gd->of_root); + np = of_node_get(root); while (np && *path == '/') { struct device_node *tmp = np; @@ -791,7 +797,7 @@ int of_alias_scan(void) name = of_get_property(of_chosen, "stdout-path", NULL); if (name) - of_stdout = of_find_node_opts_by_path(name, + of_stdout = of_find_node_opts_by_path(NULL, name, &of_stdout_options); } diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index a59832ebbfb..bd41ef503c2 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -552,6 +552,17 @@ ofnode ofnode_path(const char *path) return offset_to_ofnode(fdt_path_offset(gd->fdt_blob, path)); } +ofnode ofnode_path_root(oftree tree, const char *path) +{ + if (of_live_active()) + return np_to_ofnode(of_find_node_opts_by_path(tree.np, path, + NULL)); + else if (*path != '/' && tree.fdt != gd->fdt_blob) + return ofnode_null(); /* Aliases only on control FDT */ + else + return offset_to_ofnode(fdt_path_offset(tree.fdt, path)); +} + const void *ofnode_read_chosen_prop(const char *propname, int *sizep) { ofnode chosen_node; diff --git a/include/dm/of_access.h b/include/dm/of_access.h index ec6e6e2c7c0..078f2ea06cd 100644 --- a/include/dm/of_access.h +++ b/include/dm/of_access.h @@ -197,6 +197,11 @@ struct device_node *of_get_parent(const struct device_node *np); /** * of_find_node_opts_by_path() - Find a node matching a full OF path * + * Note that alias processing is only available on the control FDT (gd->of_root). + * For other trees it is skipped, so any attempt to obtain an alias will result + * in returning NULL. + * + * @root: Root node of the tree to use. If this is NULL, then gd->of_root is used * @path: Either the full path to match, or if the path does not start with * '/', the name of a property of the /aliases node (an alias). In the * case of an alias, the node matching the alias' value will be returned. @@ -210,12 +215,13 @@ struct device_node *of_get_parent(const struct device_node *np); * * Return: a node pointer or NULL if not found */ -struct device_node *of_find_node_opts_by_path(const char *path, +struct device_node *of_find_node_opts_by_path(struct device_node *root, + const char *path, const char **opts); static inline struct device_node *of_find_node_by_path(const char *path) { - return of_find_node_opts_by_path(path, NULL); + return of_find_node_opts_by_path(NULL, path, NULL); } /** diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index 5a5309d79a7..d7ad5dccc14 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -176,6 +176,23 @@ static inline ofnode ofnode_root(void) return node; } +/** + * oftree_default() - Returns the default device tree (U-Boot's control FDT) + * + * Returns: reference to the control FDT + */ +static inline oftree oftree_default(void) +{ + oftree tree; + + if (of_live_active()) + tree.np = gd_of_root(); + else + tree.fdt = (void *)gd->fdt_blob; + + return tree; +} + /** * ofnode_name_eq() - Check if the node name is equivalent to a given name * ignoring the unit address @@ -640,11 +657,22 @@ int ofnode_count_phandle_with_args(ofnode node, const char *list_name, /** * ofnode_path() - find a node by full path * + * This uses the control FDT. + * * @path: Full path to node, e.g. "/bus/spi@1" * Return: reference to the node found. Use ofnode_valid() to check if it exists */ ofnode ofnode_path(const char *path); +/** + * ofnode_path_root() - find a node by full path from a root node + * + * @tree: Device tree to use + * @path: Full path to node, e.g. "/bus/spi@1" + * Return: reference to the node found. Use ofnode_valid() to check if it exists + */ +ofnode ofnode_path_root(oftree tree, const char *path); + /** * ofnode_read_chosen_prop() - get the value of a chosen property * diff --git a/include/dm/ofnode_decl.h b/include/dm/ofnode_decl.h index 7c9e43e4ad8..266253d5e33 100644 --- a/include/dm/ofnode_decl.h +++ b/include/dm/ofnode_decl.h @@ -68,5 +68,18 @@ struct ofprop { }; }; +/** + * union oftree_union - reference to a tree of device tree nodes + * + * One or other of the members is used, depending on of_live_active() + * + * @np: Pointer to roott device node, used for live tree + * @fdt: Pointer to the flat device tree, used for flat tree + */ +typedef union oftree_union { + struct device_node *np; + void *fdt; +} oftree; + #endif diff --git a/include/of_live.h b/include/of_live.h index b2b9679ae84..f59d6af3350 100644 --- a/include/of_live.h +++ b/include/of_live.h @@ -20,4 +20,20 @@ struct device_node; */ int of_live_build(const void *fdt_blob, struct device_node **rootp); +/** + * unflatten_device_tree() - create tree of device_nodes from flat blob + * + * Note that this allocates a single block of memory, pointed to by *mynodes. + * To free the tree, use free(*mynodes) + * + * unflattens a device-tree, creating the + * tree of struct device_node. It also fills the "name" and "type" + * pointers of the nodes so the normal device-tree walking functions + * can be used. + * @blob: The blob to expand + * @mynodes: The device_node tree created by the call + * Return: 0 if OK, -ve on error + */ +int unflatten_device_tree(const void *blob, struct device_node **mynodes); + #endif diff --git a/lib/of_live.c b/lib/of_live.c index 2cb0dd9c073..30cae9ab881 100644 --- a/lib/of_live.c +++ b/lib/of_live.c @@ -248,19 +248,7 @@ static void *unflatten_dt_node(const void *blob, void *mem, int *poffset, return mem; } -/** - * unflatten_device_tree() - create tree of device_nodes from flat blob - * - * unflattens a device-tree, creating the - * tree of struct device_node. It also fills the "name" and "type" - * pointers of the nodes so the normal device-tree walking functions - * can be used. - * @blob: The blob to expand - * @mynodes: The device_node tree created by the call - * Return: 0 if OK, -ve on error - */ -static int unflatten_device_tree(const void *blob, - struct device_node **mynodes) +int unflatten_device_tree(const void *blob, struct device_node **mynodes) { unsigned long size; int start; diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c index 61ae1db62d7..6a252f3f504 100644 --- a/test/dm/ofnode.c +++ b/test/dm/ofnode.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -469,3 +470,69 @@ static int dm_test_ofnode_get_phy(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_ofnode_get_phy, 0); + +/** + * make_ofnode_fdt() - Create an FDT for testing with ofnode + * + * The size is set to the minimum needed + * + * @uts: Test state + * @fdt: Place to write FDT + * @size: Maximum size of space for fdt + */ +static int make_ofnode_fdt(struct unit_test_state *uts, void *fdt, int size) +{ + ut_assertok(fdt_create(fdt, size)); + ut_assertok(fdt_finish_reservemap(fdt)); + ut_assert(fdt_begin_node(fdt, "") >= 0); + + ut_assert(fdt_begin_node(fdt, "aliases") >= 0); + ut_assertok(fdt_property_string(fdt, "mmc0", "/new-mmc")); + ut_assertok(fdt_end_node(fdt)); + + ut_assert(fdt_begin_node(fdt, "new-mmc") >= 0); + ut_assertok(fdt_end_node(fdt)); + + ut_assertok(fdt_end_node(fdt)); + ut_assertok(fdt_finish(fdt)); + + return 0; +} + +static int dm_test_ofnode_root(struct unit_test_state *uts) +{ + struct device_node *root = NULL; + char fdt[256]; + oftree tree; + ofnode node; + + /* Check that aliases work on the control FDT */ + node = ofnode_get_aliases_node("ethernet3"); + ut_assert(ofnode_valid(node)); + ut_asserteq_str("sbe5", ofnode_get_name(node)); + + ut_assertok(make_ofnode_fdt(uts, fdt, sizeof(fdt))); + if (of_live_active()) { + ut_assertok(unflatten_device_tree(fdt, &root)); + tree.np = root; + } else { + tree.fdt = fdt; + } + + /* Make sure they don't work on this new tree */ + node = ofnode_path_root(tree, "mmc0"); + ut_assert(!ofnode_valid(node)); + + /* It should appear in the new tree */ + node = ofnode_path_root(tree, "/new-mmc"); + ut_assert(ofnode_valid(node)); + + /* ...and not in the control FDT */ + node = ofnode_path_root(oftree_default(), "/new-mmc"); + ut_assert(!ofnode_valid(node)); + + free(root); + + return 0; +} +DM_TEST(dm_test_ofnode_root, UT_TESTF_SCAN_FDT); -- cgit v1.3.1 From 7b1dfc9fd7e2efed345c3fe9a2412b5300ac285e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 30 Jul 2022 15:52:12 -0600 Subject: dm: core: Prepare for updating the device tree with ofnode Add some documentation and a new flag so that we can safely enabled using the ofnode interface to write to the device tree. Signed-off-by: Simon Glass --- doc/develop/driver-model/livetree.rst | 26 ++++++++++++++++++++++++++ include/test/test.h | 2 ++ test/test-main.c | 3 ++- 3 files changed, 30 insertions(+), 1 deletion(-) (limited to 'doc/develop') diff --git a/doc/develop/driver-model/livetree.rst b/doc/develop/driver-model/livetree.rst index c29f29b205b..faf3eb5b5f0 100644 --- a/doc/develop/driver-model/livetree.rst +++ b/doc/develop/driver-model/livetree.rst @@ -224,6 +224,32 @@ support is provided for adding those) that indicates that they should be freed. Then the tree can be scanned for these 'separately allocated' nodes and properties before freeing the memory block. +The ofnode_write\_...() functions also support writing to the flat tree. Care +should be taken however, since this can change the position of node names and +properties in the flat tree, thus affecting the live tree. Generally this does +not matter, since when we fire up the live tree we don't ever use the flat tree +again. But in the case of tests, this can cause a problem. + +The sandbox tests typically run with OF_LIVE enabled but with the actual live +tree either present or absent. This is to make sure that the flat tree functions +work correctly even with OF_LIVE is enabled. But if a test modifies the flat +device tree, then the live tree can become invalid. Any live tree tests that run +after that point will use a corrupted tree, e.g. with an incorrect property name +or worse. To deal with this we use a flag UT_TESTF_LIVE_OR_FLAT then ensures +that tests which write to the flat tree are not run if OF_LIVE is enabled. Only +the live tree version of the test is run, when OF_LIVE is enabled, with +sandbox_flattree running the flat tree version. + +This is of course a work-around, even if a reasonable one. One solution to this +problem would be to make a copy of the flat tree before the test and restore it +afterwards, in the same memory location, so that the live tree pointers work +again. Another would be to regenerate the live tree if a test modified the flat +tree. + +Neither of these solutions is currently implemented, since the situation that +causes the problem can only occur in sandbox tests, is somewhat esoteric and +the UT_TESTF_LIVE_OR_FLAT flag deals with it in a reasonable way. + Multiple livetrees ------------------ diff --git a/include/test/test.h b/include/test/test.h index 0104e189f63..c888d68b1ed 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -46,6 +46,8 @@ enum { UT_TESTF_CONSOLE_REC = BIT(5), /* needs console recording */ /* do extra driver model init and uninit */ UT_TESTF_DM = BIT(6), + /* live or flat device tree, but not both in the same executable */ + UT_TESTF_LIVE_OR_FLAT = BIT(4), }; /** diff --git a/test/test-main.c b/test/test-main.c index ee38d1faea8..c0d0378c5d8 100644 --- a/test/test-main.c +++ b/test/test-main.c @@ -338,7 +338,8 @@ static int ut_run_test_live_flat(struct unit_test_state *uts, /* Run with the live tree if possible */ runs = 0; if (CONFIG_IS_ENABLED(OF_LIVE)) { - if (!(test->flags & UT_TESTF_FLAT_TREE)) { + if (!(test->flags & + (UT_TESTF_FLAT_TREE | UT_TESTF_LIVE_OR_FLAT))) { uts->of_live = true; ut_assertok(ut_run_test(uts, test, test->name)); runs++; -- cgit v1.3.1 From 228fe57ad20343dd9ff25ad103b6af3602aea602 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 30 Jul 2022 15:52:35 -0600 Subject: bootstd: Update documentation Add some documentation updates, particularly about global bootmeths. Signed-off-by: Simon Glass --- doc/develop/bootstd.rst | 88 ++++++++++++++++++++++++++++++++-------------- doc/usage/cmd/bootmeth.rst | 9 +++-- 2 files changed, 68 insertions(+), 29 deletions(-) (limited to 'doc/develop') diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst index dadd3473e5c..b8773f8339d 100644 --- a/doc/develop/bootstd.rst +++ b/doc/develop/bootstd.rst @@ -90,6 +90,12 @@ bootflows. Note: it is possible to have a bootmeth that uses a partition or a whole device directly, but it is more common to use a filesystem. +Note that some bootmeths are 'global', meaning that they select the bootdev +themselves. Examples include VBE and EFI boot manager. In this case, they +provide a `read_bootflow()` method which checks whatever bootdevs it likes, then +returns the bootflow, if found. Some of these bootmeths may be very slow, if +they scan a lot of devices. + Boot process ------------ @@ -113,6 +119,9 @@ the following command:: which scans for available bootflows, optionally listing each find it finds (-l) and trying to boot it (-b). +When global bootmeths are available, these are typically checked before the +above bootdev scanning. + Controlling ordering -------------------- @@ -270,18 +279,8 @@ Standard boot requires a single instance of the bootstd device to make things work. This includes global information about the state of standard boot. See `struct bootstd_priv` for this structure, accessed with `bootstd_get_priv()`. -Within the devicetree, if you add bootmeth devices or a system bootdev, they -should be children of the bootstd device. See `arch/sandbox/dts/test.dts` for -an example of this. - - -The system bootdev ------------------- - -Some bootmeths don't operate on individual bootdevs, but on the whole system. -For example, the EFI boot manager does its own device scanning and does not -make use of the bootdev devices. Such bootmeths can make use of the system -bootdev, typically considered last, after everything else has been tried. +Within the devicetree, if you add bootmeth devices, they should be children of +the bootstd device. See `arch/sandbox/dts/test.dts` for an example of this. .. _`Automatic Devices`: @@ -292,12 +291,11 @@ Automatic devices It is possible to define all the required devices in the devicetree manually, but it is not necessary. The bootstd uclass includes a `dm_scan_other()` function which creates the bootstd device if not found. If no bootmeth devices -are found at all, it creates one for each available bootmeth driver as well as a -system bootdev. +are found at all, it creates one for each available bootmeth driver. If your devicetree has any bootmeth device it must have all of them that you -want to use, as well as the system bootdev if needed, since no bootmeth devices -will be created automatically in that case. +want to use, since no bootmeth devices will be created automatically in that +case. Using devicetree @@ -348,6 +346,7 @@ Bootmeth drivers are provided for: - distro boot from a disk (syslinux) - distro boot from a network (PXE) - EFI boot using bootefi + - VBE - EFI boot using boot manager @@ -434,18 +433,23 @@ case, the iterator ends up with a `dev_order` array containing the bootdevs that are going to be used, with `num_devs` set to the number of bootdevs and `cur_dev` starting at 0. -Next, the ordering of bootdevs is determined, by `bootmeth_setup_iter_order()`. +Next, the ordering of bootmeths is determined, by `bootmeth_setup_iter_order()`. By default the ordering is again by sequence number, i.e. the `/aliases` node, or failing that the order in the devicetree. But the `bootmeth order` command or `bootmeths` environment variable can be used to set up an ordering. If that has been done, the ordering is in `struct bootstd_priv`, so that ordering is simply copied into the iterator. Either way, the `method_order` array it set up, -along with `num_methods`. Then `cur_method` is set to 0. +along with `num_methods`. + +Note that global bootmeths are always put at the end of the ordering. If any are +present, `cur_method` is set to the first one, so that global bootmeths are done +first. Once all have been used, these bootmeths are dropped from the iteration. +When there are no global bootmeths, `cur_method` is set to 0. At this point the iterator is ready to use, with the first bootdev and bootmeth -selected. All the other fields are 0. This means that the current partition is -0, which is taken to mean the whole device, since partition numbers start at 1. -It also means that `max_part` is 0, i.e. the maximum partition number we know +selected. Most of the other fields are 0. This means that the current partition +is 0, which is taken to mean the whole device, since partition numbers start at +1. It also means that `max_part` is 0, i.e. the maximum partition number we know about is 0, meaning that, as far as we know, there is no partition table on this bootdev. @@ -456,6 +460,10 @@ If the `BOOTFLOWF_ALL` iterator flag is set, even errors are returned as incomplete bootflows, but normally an error results in moving onto the next iteration. +Note that `bootflow_check()` handles global bootmeths explicitly, but calling +`bootmeth_get_bootflow()` on each one. The `doing_global` flag indicates when +the iterator is in that state. + The `bootflow_scan_next()` function handles moving onto the next iteration and checking it. In fact it sits in a loop doing that repeatedly until it finds something it wants to return. @@ -474,9 +482,10 @@ the least-sigificant digit on the right, counting like this: 0 0 2 0 1 0 0 1 1 - 0 1 1 + 0 1 2 1 0 0 1 0 1 + ... ======== ======= ======= The maximum value for `method` is `num_methods - 1` so when it exceeds that, it @@ -488,6 +497,31 @@ exceeds its maximum, then the next bootdev is used. In this way, iter_incr() works its way through all possibilities, moving forward one each time it is called. +Note that global bootmeths introduce a subtlety into the above description. +When `doing_global` is true, the iteration takes place only among the bootmeths, +i.e. the last column above. The global bootmeths are at the end of the list. +Assuming that they are entries 3 and 4 in the list, the iteration then looks +like this: + + ======== ======= ======= ======================================= + bootdev part method notes + ======== ======= ======= ======================================= + . . 3 doing_global = true, method_count = 5 + . . 4 + 0 0 0 doing_global = false, method_count = 3 + 0 0 1 + 0 0 2 + 0 1 0 + 0 1 1 + 0 1 2 + 1 0 0 + 1 0 1 + ... + ======== ======= ======= ======================================= + +The changeover of the value of `doing_global` from true to false is handled in +`iter_incr()` as well. + There is no expectation that iteration will actually finish. Quite often a valid bootflow is found early on. With `bootflow scan -b`, that causes the bootflow to be immediately booted. Assuming it is successful, the iteration never @@ -517,17 +551,19 @@ method `bootdev_get_bootflow()` to ask the bootdev to return a bootflow. It passes the iterator to the bootdev method, so that function knows what we are talking about. At first, the bootflow is set up in the state `BOOTFLOWST_BASE`, with just the `method` and `dev` intiialised. But the bootdev may fill in more, -e.g. updating the state, depending on what it finds. +e.g. updating the state, depending on what it finds. For global bootmeths the +`bootmeth_get_bootflow()` function is called instead of +`bootdev_get_bootflow()`. -Based on what the bootdev responds with, `bootflow_check()` either +Based on what the bootdev or bootmeth responds with, `bootflow_check()` either returns a valid bootflow, or a partial one with an error. A partial bootflow is one that has some fields set up, but did not reach the `BOOTFLOWST_READY` state. As noted before, if the `BOOTFLOWF_ALL` iterator flag is set, then all bootflows are returned, even partial ones. This can help with debugging. So at this point you can see that total control over whether a bootflow can -be generated from a particular iteration, or not, rests with the bootdev. -Each one can adopt its own approach. +be generated from a particular iteration, or not, rests with the bootdev (or +global bootmeth). Each one can adopt its own approach. Going down a level, what does the bootdev do in its `get_bootflow()` method? Let us consider the MMC bootdev. In that case the call to diff --git a/doc/usage/cmd/bootmeth.rst b/doc/usage/cmd/bootmeth.rst index 9fc7ebf0abf..29d8215a0c0 100644 --- a/doc/usage/cmd/bootmeth.rst +++ b/doc/usage/cmd/bootmeth.rst @@ -31,7 +31,9 @@ scanning bootdevs, each bootmeth is tried in turn to see if it can find a valid bootflow. You can use this command to adjust the order or even to omit some boomeths. -The argument is a quoted list of bootmeths to use, by name. +The argument is a quoted list of bootmeths to use, by name. If global bootmeths +are included, they must be at the end, otherwise the scanning mechanism will not +work correctly. bootmeth list @@ -47,14 +49,15 @@ Order Seq Name Description 1 1 efi EFI boot from an .efi file 2 2 pxe PXE boot from a network device 3 3 sandbox Sandbox boot for testing - 4 4 efi_mgr EFI bootmgr flow + glob 4 efi_mgr EFI bootmgr flow ===== === ================== ================================= The fields are as follows: Order: The order in which these bootmeths are invoked for each bootdev. If this - shows as a hyphen, then the bootmeth is not in the current ordering. + shows as a hyphen, then the bootmeth is not in the current ordering. If it + shows as 'glob', then this is a global bootmeth and should be at the end. Seq: The sequence number of the bootmeth, i.e. the normal ordering if none is set -- cgit v1.3.1