[PATCH 5/5] Documentation: add barebox porter's guide
Ahmad Fatoum
a.fatoum at pengutronix.de
Thu Apr 1 07:46:16 BST 2021
From: Ahmad Fatoum <ahmad at a3f.at>
Make new porters' life easier by having a central place for porting
advice and add some initial content there.
Signed-off-by: Ahmad Fatoum <a.fatoum at pengutronix.de>
---
Documentation/devel/devel.rst | 1 +
Documentation/devel/porting.rst | 509 +++++++++++++++++++++++++++++
Documentation/devicetree/index.rst | 2 +
3 files changed, 512 insertions(+)
create mode 100644 Documentation/devel/porting.rst
diff --git a/Documentation/devel/devel.rst b/Documentation/devel/devel.rst
index f559512ca3f4..f703c3bf2767 100644
--- a/Documentation/devel/devel.rst
+++ b/Documentation/devel/devel.rst
@@ -8,6 +8,7 @@ Contents:
.. toctree::
:maxdepth: 2
+ porting
background-execution
* :ref:`search`
diff --git a/Documentation/devel/porting.rst b/Documentation/devel/porting.rst
new file mode 100644
index 000000000000..aa584de60ce8
--- /dev/null
+++ b/Documentation/devel/porting.rst
@@ -0,0 +1,509 @@
+##########################
+The barebox Porter's Guide
+##########################
+
+While barebox puts much emphasis on portability, running on bare-metal
+means that there is always machine-specific glue that needs to be provided.
+This guide shows places where the glue needs to be applied and how to go
+about porting barebox to new hardware.
+
+.. note::
+ This guide is written with mainly ARM and RISC-V barebox in mind.
+ Other architectures may differ.
+
+************
+Introduction
+************
+
+Your usual barebox binary consists of two parts. A prebootloader doing
+the bare minimum initialization and then the proper barebox binary.
+
+barebox proper
+==============
+
+This is the main part of barebox and, like a multi-platform Linux kernel,
+is platform-agnostic: The program starts, registers its drivers and tries
+to match the drivers with the devices it discovers at runtime.
+It initializes file systems and common management facilities and finally
+starts an init process. barebox knows no privilege separation and the
+init process is built into barebox.
+The default init is the :ref:`Hush`, but can be overridden if required.
+
+For such a platform-agnostic program to work, it must receive external
+input about what kind of devices are available: For example, is there a
+timer? At what address and how often does it tick? For most barebox
+architectures this hardware description is provided in the form
+of a flattened device tree (FDT). As part of barebox' initialization
+procedure, it unflattens (parses) the device tree and starts probing
+(matching) the devices described within with the drivers that are being
+registered.
+
+The device tree can also describe the RAM available in the system. As
+walking the device tree itself consumes RAM, barebox proper needs to
+be passed information about an initial memory region for use as stack
+and for dynamic allocations. When barebox has probed the memory banks,
+the whole memory will become available.
+
+As result of this design, the same barebox proper binary can be reused for
+many different boards. Unlike Linux, which can expect a bootloader to pass
+it the device tree, barebox *is* the bootloader. For this reason, barebox
+proper is prefixed with what is called a prebootloader (PBL). The PBL
+handles the low-level details that need to happen before invoking barebox
+proper.
+
+Prebootloader (PBL)
+===================
+
+The :ref:`prebootloader <pbl>` is a small chunk of code whose objective is
+to prepare the environment for barebox proper to execute. This means:
+
+ - Setting up a stack
+ - Determining a memory region for initial allocations
+ - Provide the device tree
+ - Jump to barebox proper
+
+The prebootloader often runs from a constrained medium like a small
+(tens of KiB) on-chip SRAM or sometimes even directly from flash.
+
+If the size constraints allow, the PBL will contain the barebox proper
+binary in compressed form. After ensuring any external DRAM can be
+addressed, it will unpack barebox proper there and call it with the
+necessary arguments: an initial memory region and the FDT.
+
+If this is not feasible, the PBL will contain drivers to chain load
+barebox proper from the storage medium. As this is usually the same
+storage medium the PBL itself was loaded from, shortcuts can often
+be taken: e.g. a SD-Card could already be in the correct mode, so the
+PBL driver can just read the blocks without having to reinitialize
+the SD-card.
+
+barebox images
+==============
+
+In a typical build, the barebox build process generates multiple images
+(:ref:`multi_image`). All enabled PBLs are linked with the same barebox
+proper binary and then the resulting image are processed to be in the
+format expected by the loader.
+
+The loader is often a BootROM, but maybe another first stage bootloader
+or a hardware debugger.
+
+Let us now put these new concepts into practice. We will start by adding
+a new board for a platform, for which similar boards already exist.
+Then we'll look at adding a new SoC, then a new SoC family and finally
+a new architecture.
+
+**********************
+Porting to a new board
+**********************
+
+.. note::
+ Parts of this guide are taken from this ELC-E 2020 talk:
+ https://www.youtube.com/watch?v=Oj7lKbFtyM0
+
+Chances are there's already a supported board similar to yours, e.g.
+an evaluation kit from the vendor. Take a look at ``arch/$ARCH/boards/``
+and do likewise for you board. The main steps would be:
+
+Entry point
+===========
+
+The PBL's entry point is the first of your code that's run. What happens
+there depends on the previously running code. If a previous stage has already
+set up a stack and initialized the DRAM, the only thing you need to do
+is to call the common PBL code with a memory region and your device tree blob::
+
+ ENTRY_FUNCTION(start_my_board, r0, r1, r2)
+ {
+ extern char __dtb_my_board_start[];
+ void *fdt;
+
+ relocate_to_current_adr();
+ setup_c();
+
+ pbl_set_putc(my_serial_putc, (void *)BASE_ADDR);
+
+ barebox_arm_entry(0x80000000, SZ_256M, __dtb_my_board_start);
+ }
+
+Lets look at this line by line:
+
+ - ``ENTRY_FUNCTION(start_my_board, r0, r1, r2)``
+ The entry point is special: It needs to be located at the beginning of the
+ image, it does not return and may run before a stack is set up.
+ The ``ENTRY_POINT()`` macro takes care of these details and passes along
+ a number of registers, in case the Boot ROM has placed something interesting there.
+
+ - ``extern char __dtb_my_board_start[];``
+ When a device tree is built as part of the PBL, ``__dtb_*_start`` and
+ ``__dtb_*_end`` will be defined for it. Declare the start variable, so
+ you can pass along the address of the device tree.
+
+ - ``relocate_to_current_adr();``
+ Machine code contains a mixture of relative and absolute addressing.
+ Because the PBL doesn't know in advance which address it's loaded to,
+ the link address of global variables may not be correct. To correct
+ them a runtime offset needs to be added, so they point at the
+ correct location. This procedure is called relocation and is achieved
+ by this function. Note that this is self-modifying code, so it's not
+ safe to call this when executing in-place from flash or ROM.
+
+ - ``setup_c();``
+ As a size optimization, zero-initialized variables of static storage
+ duration are not written to the executable. Instead only the region
+ where they should be located is described and at runtime that region
+ is zeroed. This is what ``setup_c()`` does.
+
+ - ``pbl_set_putc(my_serial_putc, (void *)BASE_ADDR);``
+ Now that we have a C environment set up, lets set our first global
+ variable. ``pbl_set_putc`` saves a function pointer that can be used
+ to output a single character. This can be used for the early PBL
+ console to output messages even before any drivers are initialized.
+
+ - ``barebox_arm_entry`` will compute a new stack top from the supplied memory
+ region and uncompress barebox proper and pass along its arguments.
+
+Looking at other boards you might see some different patterns:
+
+ - ``*_cpu_lowlevel_init();``: Often some common initialization and quirk handling
+ needs to be done at start. If a board similar to yours does this, you probably
+ want to do likewise.
+
+ - ``__dtb_z_my_board_start[];``: Because the PBL normally doesn't parse anything out
+ of the device tree blob, boards can benefit from keeping the device tree blob
+ compressed and only unpack it in barebox proper. Such LZO-compressed device trees
+ are prefixed with ``__dtb_z_``. It's usually a good idea to use this.
+
+ - ``imx6q_barebox_entry(...);`` Sometimes it's possible to query the memory
+ controller for the size of RAM. If there are SoC-specific helpers to achieve
+ this, you should use them.
+
+ - ``get_runtime_offset()/global_variable_offset()`` returns the difference
+ between the link and load address. This is zero after relocation, but the
+ function can be useful to pass along the correct address of a variable when
+ relocation has not yet occurred. If you need to use this for anything more
+ then passing along the FDT address, you should reconsider and probably rather
+ call ``relocate_to_current_adr();``.
+
+ - ``*_start_image(...)/*_load_image(...)/*_xload_*(...)``:
+ If the SRAM couldn't fit both PBL and the compressed barebox proper, PBL
+ will need to chainload full barebox binary from disk.
+
+Repeating previous advice: The specifics about how different SoCs handle
+things can vary widely. You're best served by mimicking a similar recently
+added board if one exists. If there's none, continue reading the following
+sections.
+
+Board code
+==========
+
+If you need board-specific setup that's not covered by any upstream device
+tree binding, you can write a driver that matches against your board's
+``/compatible``::
+
+ static int my_board_probe(struct device_d *dev)
+ {
+ /* Do some board-specific setup */
+ return 0;
+ }
+
+ static const struct of_device_id my_board_of_match[] = {
+ { .compatible = "my,cool-board" },
+ { /* sentinel */ },
+ };
+
+ static struct driver_d my_board_driver = {
+ .name = "board-mine",
+ .probe = my_board_probe,
+ .of_compatible = my_board_of_match,
+ };
+ device_platform_driver(my_board_driver);
+
+Keep what you do here to a minimum. Many thing traditionally done here
+should rather happen in the respective drivers (e.g. PHY fixups).
+
+Device-Tree
+===========
+
+barebox regularly synchronizes its ``/dts/src`` directory with the
+upstream device trees in Linux. If your device tree happens to already
+be there you can just include it::
+
+ #include <arm/stm32mp157c-odyssey.dts>
+ #include "stm32mp151.dtsi"
+
+ / {
+ chosen {
+ environment-emmc {
+ compatible = "barebox,environment";
+ device-path = &sdmmc2, "partname:barebox-environment";
+ };
+ };
+ };
+
+ &phy0 {
+ reset-gpios = <&gpiog 0 GPIO_ACTIVE_LOW>;
+ };
+
+Here, the upstream device tree is included, then a barebox-specific
+SoC device tree ``"stm32mp151.dtsi"`` customizes it. The device tree
+adds some barebox-specific info like the environment used for storing
+persistent data during development. If the upstream device tree lacks
+some info which are necessary for barebox there can be added here
+as well. Refer to :ref:`bareboxdt` for more information.
+
+Boilerplate
+===========
+
+A number of places need to be informed about the new board:
+
+ - Either ``arch/$ARCH/Kconfig`` or ``arch/$ARCH/mach-$platform/Kconfig``
+ needs to define a Kconfig symbol for the new board
+ - ``arch/$ARCH/boards/Makefile`` needs to be told which directory the board
+ code resides in
+ - ``arch/$ARCH/dts/Makefile`` needs to be told the name of the device tree
+ to be built
+ - ``images/Makefile.$platform`` needs to be told the name of the entry point(s)
+ for the board
+
+Example::
+
+ --- /dev/null
+ +++ b/arch/arm/boards/seeed-odyssey/Makefile
+ +lwl-y += lowlevel.o
+ +obj-y += board.o
+
+ --- a/arch/arm/mach-stm32mp/Kconfig
+ +++ b/arch/arm/mach-stm32mp/Kconfig
+ +config MACH_SEEED_ODYSSEY
+ + select ARCH_STM32MP157
+ + bool "Seeed Studio Odyssey"
+
+ --- a/arch/arm/boards/Makefile
+ +++ b/arch/arm/boards/Makefile
+ +obj-$(CONFIG_MACH_SEEED_ODYSSEY) += seeed-odyssey/
+
+ --- a/arch/arm/dts/Makefile
+ +++ b/arch/arm/dts/Makefile
+ +lwl-$(CONFIG_MACH_SEEED_ODYSSEY) += stm32mp157c-odyssey.dtb.o
+
+ --- a/images/Makefile.stm32mp
+ +++ b/images/Makefile.stm32mp
+ $(obj)/%.stm32: $(obj)/% FORCE
+ $(call if_changed,stm32_image)
+
+ STM32MP1_OPTS = -a 0xc0100000 -e 0xc0100000 -v1
+
+ +pblb-$(CONFIG_MACH_SEEED_ODYSSEY) += start_stm32mp157c_seeed_odyssey
+ +FILE_barebox-stm32mp157c-seeed-odyssey.img = start_stm32mp157c_seeed_odyssey.pblb.stm32
+ +OPTS_start_stm32mp157c_seeed_odyssey.pblb.stm32 = $(STM32MP1_OPTS)
+ +image-$(CONFIG_MACH_SEEED_ODYSSEY) += barebox-stm32mp157c-seeed-odyssey.img
+
+********************
+Porting to a new SoC
+********************
+
+So, barebox supports the SoC's family, but not this particular SoC.
+For example, the new fancy network controller is lacking support.
+
+.. note::
+ If your new SoC requires early boot drivers, like e.g. memory
+ controller setup. Refer to the next section.
+
+Often drivers can be ported from other projects. Candidates are
+the Linux kernel, the bootloader maintained by the vendor or other
+projects like Das U-Boot, Zephyr or EDK.
+
+Porting from Linux is often straight-forward, because barebox
+imports many facilities from Linux. A key difference is that
+barebox does not utilize interrupts, so kernel code employing them
+needs to be modified into polling for status change instead.
+In this case, porting from U-Boot may be easier if a driver already
+exists. Usually, ported drivers will be a mixture of both if they're
+not written from scratch.
+
+Drivers should probe from device tree and use the same bindings
+like the Linux kernel. If there's no upstream binding, the barebox
+binding should be documented and prefixed with ``barebox,``.
+
+Considerations when writing Linux drivers also apply to barebox:
+
+ * Avoid use of ``#ifdef HARDWARE``. Multi-image code should detect at
+ runtime what hardware it is, preferably through the device tree
+
+ * Don't use ``__weak`` symbols for ad-hoc plugging in of code. They
+ make code harder to reason about and clash with multi-image.
+
+ * Write drivers so they can be instantiated more than once
+
+ * Modularize. Describe inter-driver dependency in the device tree
+
+Miscellaneous Linux porting advice:
+
+ * Branches dependent on ``system_state``: Take the ``SYSTEM_BOOTING`` branch
+ * ``struct clk_hw``: rename to ``struct clk``
+ * ``struct of_clk_hw_simple_get``: rename to ``struct of_clk_src_simple_get``
+ * ``usleep`` and co.: use ``[mud]elay``
+ * ``.of_node``: use ``.device_node``
+ * ``jiffies``: use ``get_time_ns()``
+ * ``time_before``: use ``!is_timeout()``
+ * ``clk_hw_register_fixed_rate_with_accuracy``: use ``clk_register_fixed_rate`` without accuracy
+ * ``CLK_SET_RATE_GATE`` can be ignored
+ * ``clk_prepare``: is for the non-atomic code preparing for clk enablement. Merge it into ``clk_enable``
+
+***************************
+Porting to a new SoC family
+***************************
+
+Extending support to a new SoC family can involve a number of things:
+
+New header format
+=================
+
+Your loader may require a specific header or format. If the header is meant
+to be executable, it should preferably be added as inline assembly to
+the start of the PBL entry points. See ``__barebox_arm_head`` and
+``__barebox_riscv_header``. Otherwise, add a new tool to ``scripts/``
+and have it run as part the image build process. ``images/`` contains
+various examples.
+
+Memory controller setup
+=======================
+
+If you've an external DRAM controller, you will need to configure it.
+This may involve enabling clocks and PLLs. This should all happen
+in the PBL entry point.
+
+Chainloading
+============
+
+If the whole barebox image couldn't be loaded initially due to size
+constraints, the prebootloader must arrange for chainloading the full
+barebox image.
+
+One good way to go about it is to check whether the program counter
+is in DRAM or SRAM. If in DRAM, we can assume that the image was loaded
+in full and we can just go into the common PBL entry and extract barebox
+proper. If in SRAM, we'll need to load the remainder from the boot medium.
+
+This loading requires the PBL to have a driver for the boot medium as
+well as its prerequisites like clocks, resets or pin multiplexers.
+
+Examples for this are the i.MX xload functions. Some BootROMs boot from
+a FAT file system. There is vfat support in the PBL. Refer to the sama5d2
+baord support for an example.
+
+Core drivers
+============
+
+barebox contains some stop-gap alternatives that can be used before
+dedicated drivers are available:
+
+ * Clocksource: barebox often needs to delay for a specific time.
+ ``CLOCKSOURCE_DUMMY_RATE`` can be used as a stop-gap solution
+ during initial bring up.
+
+ * Console driver: serial output is very useful for debugging. Stop-gap
+ solution can be ``DEBUG_LL`` console
+
+*****************************
+Porting to a new architecture
+*****************************
+
+Makefile
+========
+
+``arch/$ARCH/Makefile`` defines how barebox is built for the
+architecture. Among other things, it configures which compiler
+and linker flags to use and which directories Kbuild should
+descend into.
+
+Kconfig
+=======
+
+``arch/$ARCH/Kconfig`` defines the architecture's main Kconfig symbol,
+the supported subarchitectures as well as other architecture specific
+options. New architectures should select ``OFTREE`` and ``OFDEVICE``
+as well as ``HAVE_PBL_IMAGE`` and ``HAVE_PBL_MULTI_IMAGES``.
+
+Header files
+============
+
+Your architecture needs to implement following headers:
+
+ - ``<asm/bitops.h>``
+ Defines optimized bit operations if available
+ - ``<asm/bitsperlong.h>``
+ ``sizeof(long)`` Should be the size of your pointer
+ - ``<asm/byteorder.h>``
+ If the compiler defines a macro to indicate endianness,
+ use it here.
+ - ``<asm/elf.h>``
+ If using ELF relocation entries
+ - ``<asm/dma.h>``
+ Only if ``HAS_DMA`` is selected by the architecture.
+ - ``<asm/io.h>``
+ Defines I/O memory and port accessors
+ - ``<asm/mmu.h>``
+ - ``<asm/string.h>``
+ - ``<asm/swab.h>``
+ - ``<asm/types.h>``
+ - ``<asm/unaligned.h>``
+ Defines accessors for unaligned access
+ - ``<asm/setjmp.h>``
+ Must define ``setjmp``, ``longjmp`` and ``initjmp``.
+ ``setjmp`` and ``longjmp`` can be taken out of libc. As barebox
+ does no floating point operations, saving/restoring these
+ registers can be dropped. ``initjmp`` is like ``setjmp``, but
+ only needs to store 2 values in the ``jmpbuf``:
+ new stack top and address ``longjmp`` should branch to
+
+Most of these headers can be implemented by referring to the
+respective ``<asm-generic/*.h>`` versions.
+
+Relocation
+==========
+
+Because there might be no single memory region that works for all
+images in a multi-image build, barebox needs to be relocatable.
+This can be done by implementing three functions:
+
+ - ``get_runtime_offset()``: This function should return the
+ difference between the link and load address. One easy way
+ to implement this is to force the link address to ``0`` and to
+ determine the load address of the barebox ``_text`` section.
+
+ - ``relocate_to_current_adr()``: This function walks through
+ the relocation entries and fixes them up by the runtime
+ offset. After this is done ``get_runtime_offset()`` should
+ return `0` as ``_text`` should also be fixed up by it.
+
+ - ``relocate_to_adr()``: This function copies the running barebox
+ to a new location in RAM, then does ``relocate_to_current_adr()``
+ and resumes execution at the new location. This can be omitted
+ if barebox won't initially execute out of ROM.
+
+Of course, for these functions to work. The linker script needs
+to ensure that the ELF relocation records are included in the
+final image and define start and end markers so code can iterate
+over them.
+
+To ease debugging, even when relocation has no yet happened,
+barebox supports ``DEBUG_LL``, which acts similarly to the
+PBL console, but does not require relocation. This is incompatible
+with multi-image, function mso this should only be considered while debugging.
+
+Linker scripts
+==============
+
+You'll need two linker scripts, one for barebox proper and the
+other for the PBL. Refer to the ARM and/or RISC-V linker scripts
+for an example.
+
+Generic DT image
+================
+
+It's a good idea to have the architecture generate an image that
+looks like and can be booted just like a Linux kernel. This allows
+easy testing with QEMU or booting from barebox or other bootloaders.
+Refer to ``BOARD_GENERIC_DT`` for examples.
diff --git a/Documentation/devicetree/index.rst b/Documentation/devicetree/index.rst
index ce62ebe10936..198c4893ff51 100644
--- a/Documentation/devicetree/index.rst
+++ b/Documentation/devicetree/index.rst
@@ -1,3 +1,5 @@
+.. _bareboxdt:
+
Barebox devicetree handling and bindings
========================================
--
2.29.2
More information about the barebox
mailing list