summaryrefslogtreecommitdiff
path: root/doc/develop/bootstd.rst
blob: 7a2a69fdfcecc0a46c55fa7b718017f69d98f616 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
.. SPDX-License-Identifier: GPL-2.0+:

U-Boot Standard Boot
====================

Introduction
------------

Standard boot provides a built-in way for U-Boot to automatically boot
an Operating System without custom scripting and other customisation. It
introduces the following concepts:

   - bootdev  - a device which can hold or access a distro (e.g. MMC, Ethernet)
   - bootmeth - a method to scan a bootdev to find bootflows (e.g. distro boot)
   - bootflow - a description of how to boot (provided by the distro)

For Linux, the distro (Linux distribution, e.g. Debian, Fedora) is responsible
for creating a bootflow for each kernel combination that it wants to offer.
These bootflows are stored on media so they can be discovered by U-Boot. This
feature is typically called `distro boot` (see :doc:`distro`) because it is
a way for distributions to boot on any hardware.

Traditionally U-Boot has relied on scripts to implement this feature. See
distro_bootcmd_ for details. This is done because U-Boot has no native support
for scanning devices. While the scripts work remarkably well, they can be hard
to understand and extend, and the feature does not include tests. They are also
making it difficult to move away from ad-hoc CONFIGs, since they are implemented
using the environment and a lot of #defines.

Standard boot is a generalisation of distro boot. It provides a more built-in
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
--------

A bootflow is a file that describes how to boot a distro. Conceptually there can
be different formats for that file but at present U-Boot only supports the
BootLoaderSpec_ format. which looks something like this::

   menu autoboot Welcome to Fedora-Workstation-armhfp-31-1.9. Automatic boot in # second{,s}. Press a key for options.
   menu title Fedora-Workstation-armhfp-31-1.9 Boot Options.
   menu hidden

   label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
       kernel /vmlinuz-5.3.7-301.fc31.armv7hl
       append ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB
       fdtdir /dtb-5.3.7-301.fc31.armv7hl/
       initrd /initramfs-5.3.7-301.fc31.armv7hl.img

As you can see it specifies a kernel, a ramdisk (initrd) and a directory from
which to load devicetree files. The details are described in distro_bootcmd_.

The bootflow is provided by the distro. It is not part of U-Boot. U-Boot's job
is simply to interpret the file and carry out the instructions. This allows
distros to boot on essentially any device supported by U-Boot.

Typically the first available bootflow is selected and booted. If that fails,
then the next one is tried.


Bootdev
-------

Where does U-Boot find the media that holds the operating systems? That is the
job of bootdev. A bootdev is simply a layer on top of a media device (such as
MMC, NVMe). The bootdev accesses the device, including partitions and
filesystems that might contain things related to an operating system.

For example, an MMC bootdev provides access to the individual partitions on the
MMC device. It scans through these to find filesystems with the boot flag set,
then provides a list of these for consideration.

Some bootdevs are not visible until a bus is enumerated, e.g. flash sticks
attached via USB. To deal with this, each bootdev has an associated 'hunter'
which can hunt for bootdevs of a particular uclass type. For example, the SCSI
bootdev scans the SCSI bus looking for devices, creating a bootdev for each
Logical Unit Number (LUN) that it finds.


Bootmeth
--------

Once the list of filesystems is provided, how does U-Boot find the bootflow
files in these filesystems. That is the job of bootmeth. Each boot method has
its own way of doing this.

For example, the distro bootmeth simply looks through the provided filesystem
for a file called `extlinux/extlinux.conf`. This files constitutes a bootflow.
If the distro bootmeth is used on multiple partitions it may produce multiple
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
------------

U-Boot tries to use the 'lazy init' approach whereever possible and distro boot
is no exception. The algorithm is::

   while (get next bootdev)
      while (get next bootmeth)
          while (get next bootflow)
              try to boot it

So U-Boot works its way through the bootdevs, trying each bootmeth in turn to
obtain bootflows, until it either boots or exhausts the available options.

Instead of 500 lines of #defines and a 4KB boot script, all that is needed is
the following command::

   bootflow scan -lb

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

Several options are available to control the ordering of boot scanning:


boot_targets
~~~~~~~~~~~~

This environment variable can be used to control the list of bootdevs searched
and their ordering, for example::

   setenv boot_targets "mmc0 mmc1 usb pxe"

Entries may be removed or re-ordered in this list to affect the boot order. If
the variable is empty, the default ordering is used, based on the priority of
bootdevs and their sequence numbers.


bootmeths
~~~~~~~~~

This environment variable can be used to control the list of bootmeths used and
their ordering for example::

   setenv bootmeths "extlinux efi"

Entries may be removed or re-ordered in this list to affect the order the
bootmeths are tried on each bootdev. If the variable is empty, the default
ordering is used, based on the bootmeth sequence numbers, which can be
controlled by aliases.

The :ref:`usage/cmd/bootmeth:bootmeth command` (`bootmeth order`) operates in
the same way as setting this variable.


Bootdev uclass
--------------

The bootdev uclass provides an simple API call to obtain a bootflows from a
device::

   int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
                            struct bootflow *bflow);

This takes a iterator which indicates the bootdev, partition and bootmeth to
use. It returns a bootflow. This is the core of the bootdev implementation. The
bootdev drivers that implement this differ depending on the media they are
reading from, but each is responsible for returning a valid bootflow if
available.

A helper called `bootdev_find_in_blk()` makes it fairly easy to implement this
function for each media device uclass, in a few lines of code. For many types
ot bootdevs, the `get_bootflow` member can be NULL, indicating that the default
handler is used. This is called `default_get_bootflow()` and it only works with
block devices.


Bootdev drivers
---------------

A bootdev driver is typically fairly simple. Here is one for mmc::

    static int mmc_bootdev_bind(struct udevice *dev)
    {
        struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);

        ucp->prio = BOOTDEVP_2_INTERNAL_FAST;

        return 0;
    }

    struct bootdev_ops mmc_bootdev_ops = {
    };

    static const struct udevice_id mmc_bootdev_ids[] = {
        { .compatible = "u-boot,bootdev-mmc" },
        { }
    };

    U_BOOT_DRIVER(mmc_bootdev) = {
        .name        = "mmc_bootdev",
        .id        = UCLASS_BOOTDEV,
        .ops        = &mmc_bootdev_ops,
        .bind        = mmc_bootdev_bind,
        .of_match    = mmc_bootdev_ids,
    };

You may notice that the `get_bootflow` memory is not provided, so is NULL. This
means that `default_get_bootflow()` is used. This simply obtains the
block device and calls a bootdev helper function to do the rest. The
implementation of `bootdev_find_in_blk()` checks the partition table, and
attempts to read a file from a filesystem on the partition number given by the
`@iter->part` parameter. If there are any bootable partitions in the table,
then only bootable partitions are considered.

Each bootdev has a priority, which indicates the order in which it is used,
if `boot_targets` is not used. Faster bootdevs are used first, since they are
more likely to be able to boot the device quickly.


Environment Variables
---------------------

Various environment variables are used by standard boot. These allow the board
to control where things are placed when booting the OS. You should ensure that
your boards sets values for these.

fdtfile
    Name of the flattened device tree (FDT) file to load, e.g.
    "rockchip/rk3399-rockpro64.dtb"

fdtaddr_addr_r
    Address at which to load the FDT, e.g. 0x01f00000

fdtoverlay_addr_r (needed if overlays are used)
    Address at which to load the overlay for the FDT, e.g. 0x02000000

kernel_addr_r
    Address at which to load the kernel, e.g. 0x02080000

kernel_comp_addr_r
    Address to which to decompress the kernel, e.g. 0x08000000

kernel_comp_size
    Size of available space for decompressed kernel, e.g. 0x2000000

pxefile_addr_r
    Address at which to load the PXE file, e.g. 0x00600000

ramdisk_addr_r
    Address at which to load the ramdisk, e.g. 0x06000000

scriptaddr
    Address at which to load the U-Boot script, e.g. 0x00500000

script_offset_f
    SPI flash offset from which to load the U-Boot script, e.g. 0xffe000

script_size_f
    Size of the script to load, e.g. 0x2000

Some variables are set by script bootmeth:

devtype
    Device type being used for boot, e.g. mmc

devnum
    Device number being used for boot, e.g. 1

distro_bootpart
    Partition being used for boot, e.g. 2

prefix
    Directory containing the script

mmc_bootdev
    Device number being used for boot (e.g. 1). This is only used by MMC on
    sunxi boards.


Device hierarchy
----------------

A bootdev device is a child of the media device. In this example, you can see
that the bootdev is a sibling of the block device and both are children of
media device::

    mmc           0  [ + ]   bcm2835-sdhost        |   |-- mmc@7e202000
    blk           0  [ + ]   mmc_blk               |   |   |-- mmc@7e202000.blk
    bootdev       0  [   ]   mmc_bootdev           |   |   `-- mmc@7e202000.bootdev
    mmc           1  [ + ]   sdhci-bcm2835         |   |-- sdhci@7e300000
    blk           1  [   ]   mmc_blk               |   |   |-- sdhci@7e300000.blk
    bootdev       1  [   ]   mmc_bootdev           |   |   `-- sdhci@7e300000.bootdev

The bootdev device is typically created automatically in the media uclass'
`post_bind()` method by calling `bootdev_setup_for_dev()` or
`bootdev_setup_sibling_blk()`. The code typically something like this::

    /* dev is the Ethernet device */
    ret = bootdev_setup_for_dev(dev, "eth_bootdev");
    if (ret)
        return log_msg_ret("bootdev", ret);

or::

    /* blk is the block device (child of MMC device)
    ret = bootdev_setup_sibling_blk(blk, "mmc_bootdev");
    if (ret)
        return log_msg_ret("bootdev", ret);


Here, `eth_bootdev` is the name of the Ethernet bootdev driver and `dev`
is the ethernet device. This function is safe to call even if standard boot is
not enabled, since it does nothing in that case. It can be added to all uclasses
which implement suitable media.


The bootstd device
------------------

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, they should be children of
the bootstd device. See `arch/sandbox/dts/test.dts` for an example of this.


.. _`Automatic Devices`:

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.

If your devicetree has any bootmeth device it must have all of them that you
want to use, since no bootmeth devices will be created automatically in that
case.


Using devicetree
----------------

If a bootdev is complicated or needs configuration information, it can be
added to the devicetree as a child of the media device. For example, imagine a
bootdev which reads a bootflow from SPI flash. The devicetree fragment might
look like this::

    spi@0 {
        flash@0 {
            reg = <0>;
            compatible = "spansion,m25p16", "jedec,spi-nor";
            spi-max-frequency = <40000000>;

            bootdev {
                compatible = "u-boot,sf-bootdev";
                offset = <0x2000>;
                size = <0x1000>;
            };
        };
    };

The `sf-bootdev` driver can implement a way to read from the SPI flash, using
the offset and size provided, and return that bootflow file back to the caller.
When distro boot wants to read the kernel it calls distro_getfile() which must
provide a way to read from the SPI flash. See `distro_boot()` at distro_boot_
for more details.

Of course this is all internal to U-Boot. All the distro sees is another way
to boot.


Configuration
-------------

Standard boot is enabled with `CONFIG_BOOTSTD`. Each bootmeth has its own CONFIG
option also. For example, `CONFIG_BOOTMETH_EXTLINUX` enables support for
booting from a disk using an `extlinux.conf` file.

To enable all feature sof standard boot, use `CONFIG_BOOTSTD_FULL`. This
includes the full set of commands, more error messages when things go wrong and
bootmeth ordering with the bootmeths environment variable.

You should probably also enable `CONFIG_BOOTSTD_DEFAULTS`, which provides
several filesystem and network features (if `CONFIG_NET` is enabled) so that
a good selection of boot options is available.


Available bootmeth drivers
--------------------------

Bootmeth drivers are provided for:

   - extlinux / syslinux boot from a disk
   - extlinux boot from a network (PXE)
   - U-Boot scripts from disk, network or SPI flash
   - EFI boot using bootefi from disk
   - VBE
   - EFI boot using boot manager


Command interface
-----------------

Three commands are available:

`bootdev`
    Allows listing of available bootdevs, selecting a particular one and
    getting information about it. See :doc:`../usage/cmd/bootdev`

`bootflow`
    Allows scanning one or more bootdevs for bootflows, listing available
    bootflows, selecting one, obtaining information about it and booting it.
    See :doc:`../usage/cmd/bootflow`

`bootmeth`
    Allow listing of available bootmethds and setting the order in which they
    are tried. See :doc:`../usage/cmd/bootmeth`

.. _BootflowStates:

Bootflow states
---------------

Here is a list of states that a bootflow can be in:

=======  =======================================================================
State    Meaning
=======  =======================================================================
base     Starting-out state, indicates that no media/partition was found. For an
         SD card socket it may indicate that the card is not inserted.
media    Media was found (e.g. SD card is inserted) but no partition information
         was found. It might lack a partition table or have a read error.
part     Partition was found but a filesystem could not be read. This could be
         because the partition does not hold a filesystem or the filesystem is
         very corrupted.
fs       Filesystem was found but the file could not be read. It could be
         missing or in the wrong subdirectory.
file     File was found and its size detected, but it could not be read. This
         could indicate filesystem corruption.
ready    File was loaded and is ready for use. In this state the bootflow is
         ready to be booted.
=======  =======================================================================


Theory of operation
-------------------

This describes how standard boot progresses through to booting an operating
system.

To start. all the necessary devices must be bound, including bootstd, which
provides the top-level `struct bootstd_priv` containing optional configuration
information. The bootstd device is also holds the various lists used while
scanning. This step is normally handled automatically by driver model, as
described in `Automatic Devices`_.

Bootdevs are also required, to provide access to the media to use. These are not
useful by themselves: bootmeths are needed to provide the means of scanning
those bootdevs. So, all up, we need a single bootstd device, one or more bootdev
devices and one or more bootmeth devices.

Once these are ready, typically a `bootflow scan` command is issued. This kicks
of the iteration process, which involves hunting for bootdevs and looking
through the bootdevs and their partitions one by one to find bootflows.

Iteration is kicked off using `bootflow_scan_first()`.

The iterator is set up with `bootflow_iter_init()`. This simply creates an
empty one with the given flags. Flags are used to control whether each
iteration is displayed, whether to return iterations even if they did not result
in a valid bootflow, whether to iterate through just a single bootdev, etc.

Then the iterator is set up to according to the parameters given:

- When `dev` is provided, then a single bootdev is scanned. In this case,
  `BOOTFLOWIF_SKIP_GLOBAL` and `BOOTFLOWIF_SINGLE_DEV` are set. No hunters are
  used in this case

- Otherwise, when `label` is provided, then a single label or named bootdev is
  scanned. In this case `BOOTFLOWIF_SKIP_GLOBAL` is set and there are three
  options (with an effect on the `iter_incr()` function described later):

  - If `label` indicates a numeric bootdev number (e.g. "2") then
    `BOOTFLOW_METHF_SINGLE_DEV` is set. In this case, moving to the next bootdev
    simple stops, since there is only one. No hunters are used.
  - If `label` indicates a particular media device (e.g. "mmc1") then
    `BOOTFLOWIF_SINGLE_MEDIA` is set. In this case, moving to the next bootdev
    processes just the children of the media device. Hunters are used, in this
    example just the "mmc" hunter.
  - If `label` indicates a media uclass (e.g. "mmc") then
    `BOOTFLOWIF_SINGLE_UCLASS` is set. In this case, all bootdevs in that uclass
    are used. Hunters are used, in this example just the "mmc" hunter

- Otherwise, none of the above flags is set and iteration is set up to work
  through `boot_targets` environment variable (or `bootdev-order` device tree
  property) in order, running the relevant hunter first. In this case
  `cur_label` is used to indicate the label being processed. If there is no list
  of labels, then all bootdevs are processed in order of priority, running the
  hunters as it goes.

With the above it is therefore possible to iterate in a variety of ways.

No attempt is made to determine the ordering of bootdevs, since this cannot be
known in advance if we are using the hunters. Any hunter might discover a new
bootdev and disturb the original ordering.

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`.

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 bootmeth 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.

With the iterator ready, `bootflow_scan_first()` checks whether the current
settings produce a valid bootflow. This is handled by `bootflow_check()`, which
either returns 0 (if it got something) or an error if not (more on that later).
If the `BOOTFLOWIF_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, by 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.

The actual 'moving on' part is implemented in `iter_incr()`. This is a fairly
simple function. It increments the first counter. If that hits its maximum, it
sets it to zero and increments the second counter. You can think of all the
counters together as a number with three digits which increment in order, with
the least-sigificant digit on the right, counting like this:

   ========    =======    =======
   bootdev     part       method
   ========    =======    =======
   0           0          0
   0           0          1
   0           0          2
   0           1          0
   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
goes back to 0 and the next `part` is considered. The maximum value for that is
`max_part`, which is initially zero for all bootdevs. If we find a partition
table on that bootdev, `max_part` can be updated during the iteration to a
higher value - see `bootdev_find_in_blk()` for that, described later. If that
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.

Note that the value in the `bootdev` column above is not actually stored - it is
just for illustration. In practice, `iter_incr()` uses the flags to determine
whether to move to the next bootdev in the uclass, the next child of the media
device, the next label, or the next priority level, depending on the flag
settings (see `BOOTFLOW_METHF_SINGLE_DEV`, etc. above).

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
completes.

Also note that the iterator hold the **current** combination being considered.
So when `iter_incr()` is called, it increments to the next one and returns it,
the new **current** combination.

Note also the `err` field in `struct bootflow_iter`. This is normally 0 and has
thus has no effect on `iter_inc()`. But if it is non-zero, signalling an error,
it indicates to the iterator what it should do when called. It can force moving
to the next partition, or bootdev, for example. The special values
`BF_NO_MORE_PARTS` and `BF_NO_MORE_DEVICES` handle this. When `iter_incr` sees
`BF_NO_MORE_PARTS` it knows that it should immediately move to the next bootdev.
When it sees `BF_NO_MORE_DEVICES` it knows that there is nothing more it can do
so it should immediately return. The caller of `iter_incr()` is responsible for
updating the `err` field, based on the return value it sees.

The above describes the iteration process at a high level. It is basically a
very simple increment function with a checker called `bootflow_check()` that
checks the result of each iteration generated, to determine whether it can
produce a bootflow.

So what happens inside of `bootflow_check()`? It simply calls the uclass
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. For global bootmeths the
`bootmeth_get_bootflow()` function is called instead of
`bootdev_get_bootflow()`.

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 `BOOTFLOWIF_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 (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
`bootdev_get_bootflow()` ends up in `default_get_bootflow()`. It locates the
parent device of the bootdev, i.e. the `UCLASS_MMC` device itself, then finds
the block device associated with it. It then calls the helper function
`bootdev_find_in_blk()` to do all the work. This is common with just about any
bootdev that is based on a media device.

The `bootdev_find_in_blk()` helper is implemented in the bootdev uclass. It
names the bootflow and copies the partition number in from the iterator. Then it
calls the bootmeth device to check if it can support this device. This is
important since some bootmeths only work with network devices, for example. If
that check fails, it stops.

Assuming the bootmeth is happy, or at least indicates that it is willing to try
(by returning 0 from its `check()` method), the next step is to try the
partition. If that works it tries to detect a file system. If that works then it
calls the bootmeth device once more, this time to read the bootflow.

Note: At present a filesystem is needed for the bootmeth to be called on block
devices, simply because we don't have any examples where this is not the case.
This feature can be added as needed. Note that sandbox is a special case, since
in that case the host filesystem can be accessed even though the block device
is NULL.

If we take the example of the `bootmeth_extlinux` driver, this call ends up at
`extlinux_read_bootflow()`. It has the filesystem ready, so tries various
filenames to try to find the `extlinux.conf` file, reading it if possible. If
all goes well the bootflow ends up in the `BOOTFLOWST_READY` state.

At this point, we fall back from the bootmeth driver, to
`bootdev_find_in_blk()`, then back to `default_get_bootflow()`, then to
`bootdev_get_bootflow()`, then to `bootflow_check()` and finally to its caller,
either `bootflow_scan_first()` or `bootflow_scan_next()`. In either case,
the bootflow is returned as the result of this iteration, assuming it made it to
the  `BOOTFLOWST_READY` state.

That is the basic operation of scanning for bootflows. The process of booting a
bootflow is handled by the bootmeth driver for that bootflow. In the case of
extlinux boot, this parses and processes the `extlinux.conf` file that was read.
See `extlinux_boot()` for how that works. The processing may involve reading
additional files, which is handled by the `read_file()` method, which is
`extlinux_read_file()` in this case. All bootmethds should support reading
files, since the bootflow is typically only the basic instructions and does not
include the operating system itself, ramdisk, device tree, etc.

The vast majority of the bootstd code is concerned with iterating through
partitions on bootdevs and using bootmethds to find bootflows.

How about bootdevs which are not block devices? They are handled by the same
methods as above, but with a different implementation. For example, the bootmeth
for PXE boot (over a network) uses `tftp` to read files rather than `fs_read()`.
But other than that it is very similar.


Tests
-----

Tests are located in `test/boot` and cover the core functionality as well as
the commands. All tests use sandbox so can be run on a standard Linux computer
and in U-Boot's CI.

For testing, a DOS-formatted disk image is used with a FAT partition on it and
a second unused partition. This is created in `setup_bootflow_image()`, with a
canned one from the source tree used if it cannot be created (e.g. in CI).


Bootflow internals
------------------

The bootstd device holds a linked list of scanned bootflows as well as the
currently selected bootdev and bootflow (for use by commands). This is in
`struct bootstd_priv`.

Each bootdev device has its own `struct bootdev_uc_plat` which holds a
list of scanned bootflows just for that device.

The bootflow itself is documented in bootflow_h_. It includes various bits of
information about the bootflow and a buffer to hold the file.


Future
------

Apart from the to-do items below, different types of bootflow files may be
implemented in future, e.g. Chromium OS support which is currently only
available as a script in chromebook_coral.


To do
-----

Some things that need to be done to completely replace the distro-boot scripts:

- add bootdev drivers for dhcp, sata, scsi, ide, virtio
- PXE boot for EFI
- support for loading U-Boot scripts

Other ideas:

- `bootflow prep` to load everything preparing for boot, so that `bootflow boot`
  can just do the boot.
- automatically load kernel, FDT, etc. to suitable addresses so the board does
  not need to specify things like `pxefile_addr_r`


.. _distro_bootcmd: https://github.com/u-boot/u-boot/blob/master/include/config_distro_bootcmd.h
.. _BootLoaderSpec: http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/
.. _distro_boot: https://github.com/u-boot/u-boot/blob/master/boot/distro.c
.. _bootflow_h: https://github.com/u-boot/u-boot/blob/master/include/bootflow.h