[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