\documentclass[aspectratio=169,obeyspaces,spaces,hyphens,dvipsnames]{beamer}
\usepackage{minted}
\usepackage{newunicodechar}
\usepackage{hyperref}

\mode<presentation>
\usetheme{Bootlin}

%%\newcommand{\codehack}[1]{{\usebeamercolor[fg]{code} {\tt #1}}}

\title{Porting U-Boot and Linux on new ARM boards: a step-by-step
  guide}
\authors{Quentin Schulz}
\email{quentin.schulz@bootlin.com}
\slidesurl{http://bootlin.com/pub/conferences/2017/elce/schulz-how-to-support-new-board-u-boot-linux}
\conference{Embedded Linux Conference Europe 2017}

\begin{document}

\begin{frame}{\insertconference}
  \fontsize{19}{19}\selectfont
  Porting U-Boot and Linux on new ARM boards:\\a step-by-step guide\\
  \begin{columns}
    \column{0.6\textwidth}
    \fontsize{15}{15}\selectfont
    Quentin Schulz \\
    Bootlin \\
    \normalsize
    {\em \insertemail}
    \column{0.4\textwidth}
    \includegraphics[width=0.8\textwidth]{common/logo-square.pdf}\\
  \end{columns}
\end{frame}

\begin{frame}{Quentin Schulz}
  \begin{columns}
    \column{0.6\textwidth}
    \begin{itemize}
    \item Embedded Linux and kernel engineer at Bootlin
      \begin{itemize}
      \item Embedded Linux {\bf expertise}
      \item {\bf Development}, consulting and training
      \item Strong open-source focus
      \item Linux kernel contributors, ARM SoC support, kernel
      maintainers
      \end{itemize}
    \item Added support in U-Boot and Linux kernel for an i.MX6 custom
    board,
    \end{itemize}
    \column{0.4\textwidth}
    \includegraphics[width=\textwidth]{common/logo-square.pdf}\\
  \end{columns}
\end{frame}

\begin{frame}{Preamble}
  \begin{itemize}
    \item Feedback from my journey to support a custom board in U-Boot
    and Linux,
    \item Examples for a board with a well-known SoC (i.MX6) and
    already supported IPs, almost no coding skill involved in this
    talk,
    \item More focused on the U-Boot part,
%Lots of talks and documentation on supporting new boards in Linux
  \end{itemize}
\end{frame}

\begin{frame}{Golden rules}
  \begin{itemize}
    \item If you have the sources of your BSP, compile and run the BSP
    to:
      \begin{enumerate}
        \item Validate the IP you're working on works with some code,
        \item Have a reference code,
        \item Have a code that you can use to debug,
      \end{enumerate}
    \item Focus on correctly configuring RAM and UART only,
    \item Commit,
    \item One IP at a time,
    \item Commit,
  \end{itemize}
\end{frame}

\begin{frame}{Custom board presentation}
\begin{itemize}
  \item i.MX6-based module with an extension board,
  \item Ethernet, I2C, SPI, NAND, eMMC, SD Card reader, USB device,
  EEPROM, GPIO, UART, audio (I2S), HDMI, LVDS, PCIe, USB host, RTC,
  PMIC,
\end{itemize}
\end{frame}

\section{U-Boot porting}

\begin{frame}{U-Boot status}
  \begin{itemize}
    \item On-going migration from board header file defines to Kconfig
    options,
    \item On-going migration from \textit{manual} drivers probing to Driver
    Model,
  \end{itemize}
\end{frame}

\begin{frame}{U-Boot directories}
  \begin{itemize}
    \item \textit{arch/}\\
anything arch or platform related: DTS, CPU init, pinmux controller,
DRAM, clocks, ...
    \item \textit{board/}\\
code board specific (init, pinmuxing configuration, etc), Kconfig file
specifying board header file, board file, paths, Makefile for board file,
    \item \textit{configs/}\\
all boards' defconfigs
    \item \textit{drivers/}\\
    \item \textit{include/}\\
all headers
    \item \textit{include/configs/}\\
all boards' header files
    \item ...
  \end{itemize}
\end{frame}

\begin{frame}{U-Boot new board support workflow}
  \begin{enumerate}
    \item Create the board file,
    \item Create the board Kconfig file,
    \item Create the board Makefile,
    \item Create the board defconfig,
    \item Create the board header file,
    \item Source board's Kconfig in the architecture's Kconfig,
    \item Define the TARGET Kconfig option in its CPU's Kconfig,
  \end{enumerate}
  \begin{itemize}
    \item some platforms (e.g. sunxi (Allwinner)) share common files
    so only a defconfig would be required,
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{1. Create the board file}
\begin{block}{board/my\_vendor/my\_board/my\_board.c}
\fontsize{8}{8}\selectfont
%board_init contains the init sequence specific to my_board
%(pinmuxing, regulators, gpios, etc.)
\begin{minted}{c}
#include <...>

DECLARE_GLOBAL_DATA_PTR;

int dram_init(void)
{
  gd->ram_size = imx_ddr_size();
  return 0;
}

int board_init(void)
{
  return 0;
}
\end{minted}
\end{block}
\end{frame}

\begin{frame}{Global data}
  \begin{itemize}
    \item \code{DECLARE_GLOBAL_DATA_PTR},
    \item usable in code with \code{gd} global variable,
    \item on ARM, equals to hardware register \textit{r9} for ARM32
    and \textit{x18} for ARM64,
    \item used to store info in \textit{"some memory which is available very
    early after boot to allow for a minimum set of global variables
    during system initialization (until we have set up the memory
    controller so that we can use RAM)"},
    \item \code{include/asm-generic/global_data.h} to find what kind
    of information it can store,
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{2. Create the board Kconfig file}
\begin{block}{board/my\_vendor/my\_board/Kconfig}
\fontsize{8}{8}\selectfont
\begin{minted}{kconfig}
if TARGET_MY_BOARD

config SYS_BOARD
        default "my_board"

config SYS_VENDOR
        default "my_vendor"

config SYS_CONFIG_NAME
        default "my_board"

endif
\end{minted}
\end{block}
\end{frame}

\begin{frame}{Kconfig options}
  \begin{itemize}
    \item \code{SYS_VENDOR} and \code{SYS_BOARD} are used to identify the directory
    where \code{make} find the files it needs to compile,
      \begin{itemize}
        \item if both are present,
          \begin{itemize}
            \item \code{board/SYS_VENDOR/SYS_BOARD/}
          \end{itemize}
        \item if \code{SYS_VENDOR} is omitted,
          \begin{itemize}
            \item \code{board/SYS_BOARD/}
          \end{itemize}
        \item if \code{SYS_BOARD} is omitted,
          \begin{itemize}
            \item \code{board/SYS_VENDOR/common/}
          \end{itemize}
      \end{itemize}
    \item \code{SYS_CONFIG_NAME} is used to identify the board header file,
      \begin{itemize}
        \item \code{include/configs/SYS_CONFIG_NAME.h}
      \end{itemize}
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{3. Create the board Makefile}
\begin{block}{board/my\_vendor/my\_board/Makefile}
\fontsize{8}{8}\selectfont
\begin{minted}{make}
obj-y  := my_board.o
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{4. Create the board defconfig}
\begin{block}{configs/my\_board\_defconfig}
\fontsize{8}{8}\selectfont
\begin{minted}{bash}
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MY_BOARD=y
CONFIG_MXC_UART=y
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{4. Create the board defconfig}
\begin{itemize}
  \item put here anything that is selectable in Kconfig (menuconfig),
  \item drivers, features, U-Boot behaviour, libs, etc.
\end{itemize}
\end{frame}

\begin{frame}[fragile]{5. Create the board header file (minimal
example for i.MX6 Solo)}
\begin{block}{include/configs/my\_board.h}
\fontsize{8}{8}\selectfont
%anything that isn't selectable in Kconfig
%take inspiration from boards with the same SoC family
%try, fail and repeat workflow to discover which defines to set to
%have a minimal working board header file
%grep is your best friend
%you'll need the datasheet and/or BSP code
\begin{minted}{c}
#ifndef __MY_BOARD_CONFIG_H__
#define __MY_BOARD_CONFIG_H__

#define CONFIG_MXC_UART_BASE           UART5_BASE

#include "mx6_common.h"

#define CONFIG_NR_DRAM_BANKS           1
#define CONFIG_SYS_MAX_FLASH_BANKS     1
#define CONFIG_SYS_MALLOC_LEN          (10 * SZ_1M)
#define CONFIG_SYS_FSL_ESDHC_ADDR      0
#define PHYS_SDRAM                     MMDC0_ARB_BASE_ADDR
#define CONFIG_SYS_SDRAM_BASE          PHYS_SDRAM
#define CONFIG_SYS_INIT_RAM_ADDR       IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE       IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \
  (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)

#define CONFIG_SYS_INIT_SP_ADDR \
  (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
#endif
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{6. Source board's Kconfig file}
\begin{block}{arch/arm/Kconfig or\\arch/arm/mach-imx/mx6/Kconfig}
\fontsize{8}{8}\selectfont
\begin{minted}{kconfig}
...
source "board/imx31_phycore/Kconfig"
source "board/isee/igep003x/Kconfig"
source "board/my_vendor/my_board/Kconfig"
source "board/olimex/mx23_olinuxino/Kconfig"
source "board/phytec/pcm051/Kconfig"
...
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{7. Define board's TARGET Kconfig option}
\begin{block}{arch/arm/mach-imx/mx6/Kconfig}
\fontsize{8}{8}\selectfont
%selects non visible Kconfig options
\begin{minted}{kconfig}
choice
...
config TARGET_MY_BOARD
    bool "My awesome board"
    select MX6S
...
endchoice
\end{minted}
\end{block}
\end{frame}

\begin{frame}{U-Boot init sequence}
\begin{itemize}
  \item U-Boot will run two lists of functions whose purpose is to init
  or configure specific IPs before the user have access to the
  console,
  \item the first list is defined in \code{common/board_f.c} in the
  \code{static init_fnc_t init_sequence_f[]} array,
  \item first list takes care of initialising DRAM, mapping it and
  relocating the bootloader code once it's working,
  \item the second list is defined in \code{common/board_r.c} in the
  \code{static init_fnc_t init_sequence_r[]} array,
  \item some functions are run only when a constant is defined (e.g.
\code{CONFIG_BOARD_EARLY_INIT_F} defined to run
\code{board_early_init_f()}),
  \item any function returning a non-zero value will stop the init
  sequence and make U-Boot fail to boot,
  \begin{itemize}
    \item define \code{DEBUG} when having trouble with init sequence,
  \end{itemize}
  \item not all "features" are available in all functions (i.e. no
  \code{udelay} in \code{board_early_init_f()})
\end{itemize}
\end{frame}

\begin{frame}[fragile]{Driver selection}
\begin{itemize}
  \item take inspiration from boards with the same IP,
  \item inspect drivers in the appropriate subsystem,
  \begin{enumerate}
    \item focus on the driver's behaviour,
    \item then check out the registers, bit offsets, masks, etc.
    \item check for undefined macros or constants,
    \item check for piece of code surrounded by \code{ifdef} blocks,
  \end{enumerate}
  \item look for the object file of this driver in the Makefile of the
  subsystem,\\
\begin{block}{}
\fontsize{8}{8}\selectfont
\begin{minted}{make}
obj-$(CONFIG_MY_DRIVER) += my_driver.o
\end{minted}
\end{block}
  \item \code{grep} for \code{CONFIG_MY_DRIVER},
  \begin{itemize}
    \item visible symbol in some Kconfig file => add to board defconfig,
    \item non-visible symbol in some Kconfig file or not defined => board header
    file,
  \end{itemize}
  \item make sure your driver is compiled (look for
                                           \code{my_driver.o}),
\end{itemize}
\end{frame}

\begin{frame}{Driver selection - NAND example}
\begin{itemize}
  \item \code{drivers/mtd/nand/nand_mxs.c},
  \item \code{CONFIG_NAND_MXS} for compiling the driver,
  \item \code{CONFIG_SYS_MAX_NAND_DEVICE} and
  \code{CONFIG_SYS_NAND_BASE} constants for configuring the device,
\end{itemize}
\end{frame}

\begin{frame}[fragile]{Driver selection - NAND example}
\begin{block}{configs/my\_board\_defconfig}
\fontsize{8}{8}\selectfont
\begin{minted}{bash}
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MY_BOARD=y
CONFIG_MXC_UART=y
CONFIG_NAND_MXS=y
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{Driver selection - NAND example}
\begin{block}{include/configs/my\_board.h}
\fontsize{8}{8}\selectfont
\begin{minted}{c}
...
/* Define NAND settings */
/* Max number of NAND devices supported */
#define CONFIG_SYS_MAX_NAND_DEVICE     1
#define CONFIG_SYS_NAND_BASE           0x00112000
...
\end{minted}
\end{block}
\end{frame}

\begin{frame}[fragile]{Driver selection - NAND example}
\begin{block}{board/my\_vendor/my\_board.c}
\fontsize{8}{8}\selectfont
\begin{minted}{c}
...
static iomux_v3_cfg_t const nand_pads[] = {...};
...
int board_init(void)
{
  imx_iomux_v3_setup_multiple_pads(nand_pads, ARRAY_SIZE(nand_pads));

  return 0;
}
\end{minted}
\end{block}
\end{frame}

\begin{frame}{Note on Device Trees}
  \begin{itemize}
    \item the migration to Device Trees started back in 2012 and
    the code is slowly migrated, driver by driver, subsystem by subsystem,
    \item need for Driver model to use device trees,
    \item most drivers have big \code{ifdef} blocks for
    \code{CONFIG_DM},
    \begin{itemize}
      \item you can't really chose on a per-driver basis to enable DM
      support,
%...because of those big ifdefs blocks on CONFIG_DM
    \end{itemize}
    \item idem for subsystem core code,
    \item I didn't go really deep into it as we needed either no or
    full support for Device Trees and the NAND framework isn't
    migrated to the DM,
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{Effort needed to support U-Boot}
\begin{itemize}
  \item Ethernet, EEPROM, NAND, eMMC, SD Card reader, USB device,
  GPIO, UART, audio (I2S), PMIC,
\end{itemize}
\begin{block}{}
\fontsize{8}{8}\selectfont
\begin{minted}{bash}
$ wc -l board/my_vendor/my_board/* configs/my_board_defconfig include/configs/my_board.h
     15 board/my_vendor/my_board/Kconfig
      8 board/my_vendor/my_board/Makefile
    159 board/my_vendor/my_board/my_board.cfg #DCD conf for DDR controller and memory
    218 board/my_vendor/my_board/my_board.c
     39 configs/my_board_defconfig
    110 include/configs/my_board.h
    510 total
\end{minted}
\end{block}
\begin{itemize}
  \item +1 line in \code{arch/arm/Kconfig},
  \item +4 lines in \code{arch/arm/cpu/armv7/mx6/Kconfig},
  \item actually \textasciitilde{}100 lines of "real" code (PHY and
  board init, mmc configuration),
  \item no modification of U-Boot source code otherwise,
\end{itemize}
\end{frame}

\begin{frame}{Effort needed to update U-Boot}
\begin{itemize}
  \item got some weird bug with the RSA lib when checking fitImage
  signatures in U-Boot,
  \item update from 2017.03 to 2017.07 was very easy:
  \begin{itemize}
    \item \textit{port} board header file, board file, board Makefile,
    board Kconfig and update the Kconfig of the architecture,
    \item make sure options defined in board header files are not
    Kconfig options now,
  \end{itemize}
  \item problem solved in \textasciitilde{}30min by updating,
\end{itemize}
\end{frame}

\begin{frame}{Problems encountered}
\begin{itemize}
  \item board init sequence is toggling a few GPIOs with a given
  timing,
  \item all signals (even UART) go through the FPGA,
  \item failing board init sequence, no FPGA, no UART, no hair left on my
  head,
  \item no \code{udelay} in \code{board_early_init_f},
  \item a workaround was to use a forloop with \code{cpurelax()},
\end{itemize}
\end{frame}

\section{Linux kernel porting}

\begin{frame}{Linux kernel new board support workflow}
\begin{enumerate}
  \item Create the board's Device Tree,
  \item Add your board's DTB to the architecture DTS Makefile,
  \item Create a defconfig for your board,
\end{enumerate}
\end{frame}

\begin{frame}{Device Tree}
\begin{itemize}
  \item a file in a special DTS (Device Tree Source) format,
  \item purely describes the hardware of your board,
  \item matches an IP with a driver thanks to \code{compatible}
  strings,
  \item documentation can be found in
  \code{Documentation/devicetree/bindings},
  \item for a more in-depth explanation on what a Device Tree is and
  how to write it:
  \url{https://www.youtube.com/watch?v=m_NyYEBxfn8},
\end{itemize}
\end{frame}

\begin{frame}{1. Create the board's Device Tree}
\begin{enumerate}
  \item write a map of your IPs' relationships,
  \item find the SoC DTSI that you'll include,
  \item look for IPs' drivers in the correct subsystem,
  \begin{itemize}
    \item \code{grep}ing the (code)name of the IP is usually a good
    start,
  \end{itemize}
  \item once found, look for the compatible string in
  \code{Documentation/devicetree/bindings},
  \item follow the documentation to add the correct binding,
  \item some bindings are framework-wide defined so make sure to read
  the documentation of the framework involved,
  \item for SoC IPs, correct binding is usually as simple as enabling
  them and setting the correct power supply and pinmuxing since most
  is already described in the SoC Device Tree,
\end{enumerate}
\end{frame}

\begin{frame}[fragile]{1. Create the board's Device Tree}
\begin{block}{arch/arm/boot/dts/imx6s-my-board.dts}
\fontsize{8}{8}\selectfont
\begin{verbatim}
#include "imx6dl.dtsi"

/ {
  model = "MyVendor myBoard";
  compatible = "my_vendor,my_board";
};

...

&pcie {
  reset-gpio = <&gpio3 18 GPIO_ACTIVE_LOW>;
  vpcie-supply = <&reg_pcie>;
  status = "okay";
};

&uart5 {
  status = "okay";
};
\end{verbatim}
\end{block}
\end{frame}

\begin{frame}[fragile]{2. Add the DTB to the arch DTS Makefile}
\begin{block}{arch/arm/boot/dts/Makefile}
\fontsize{8}{8}\selectfont
\begin{minted}{make}
dtb-$(CONFIG_MACH_KIRKWOOD) += \
#...

dtb-$(CONFIG_SOC_IMX6Q) += \
#...
     imx6qp-sabresd.dtb \
     imx6s-my-board.dtb
#...
\end{minted}
\end{block}
\end{frame}

\begin{frame}{3. Create a defconfig for your board}
\begin{enumerate}
  \item start from the SoC family defconfig (e.g.
                                       \code{imx_v6_v7_defconfig}) or
  if there isn't one, from the architecture (e.g.
                                             \code{multi_v7_defconfig}),
  \item strip the defconfig of useless SoC families, drivers and
  features,
  \item add the \code{CONFIG} of the drivers you want to compile,
  \begin{itemize}
    \item \code{grep}ing for the basename of the driver is the way to
    go,
    \item most drivers depend on subsystems or other options, you have
    to add them to your defconfig as well if your driver doesn't
    select them,
  \end{itemize}
\end{enumerate}
\end{frame}

\begin{frame}{Problems encountered}
\begin{itemize}
  \item PCIe driver was probing but not enumerating devices,
  \begin{itemize}
    \item driver was working in BSP, found out missing support for
    regulator was the culprit,
    \item quickly wrote a 40 lines patch and sent it upstream,
  \end{itemize}
  \item Ethernet driver was missing a post reset delay for the PHY
  that was set in BSP,
  \begin{itemize}
    \item quickly wrote a 20 lines patch and sent it upstream,
  \end{itemize}
\end{itemize}
\end{frame}

\begin{frame}[fragile]{Effort needed to support Linux}
\begin{itemize}
  \item Ethernet, I2C, SPI, NAND, eMMC, SD Card reader, USB device,
  EEPROM, GPIO, UART, audio (I2S), HDMI, LVDS, PCIe, USB host, RTC,
  PMIC,
\end{itemize}
\begin{block}{}
\fontsize{8}{8}\selectfont
\begin{minted}{bash}
$ wc -l arch/arm/boot/dts/imx6s-my-board.dts
arch/arm/configs/my_board_defconfig
    606 arch/arm/boot/dts/imx6s-my-board.dts
    401 arch/arm/configs/my_board_defconfig
   1007 total
\end{minted}
\end{block}
\begin{itemize}
  \item +1 line in \code{arch/arm/boot/dts/Makefile},
  \item +20 lines for regulator support in PCIe driver (now upstream),
  \item +40 lines for Ethernet PHY post reset delay (now upstream),
  \item no modification of Linux source code otherwise,
\end{itemize}
\end{frame}

\begin{frame}{Effort needed to update Linux}
\begin{itemize}
  \item got some weird bug with dual display, the display driver would
  completely crash if both HDMI and LVDS outputs were enabled at the
  same time,
  \item update from 4.9 to 4.13 was very easy:
  \begin{itemize}
    \item copy DTB from 4.9 to 4.13 and make sure the bindings haven't
    changed in-between those versions,
    \item add the one-liner in \code{arch/arm/boot/dts/Makefile},
    \item patches were already upstream so nothing else to do,
  \end{itemize}
  \item problem solved in \textasciitilde{}30min by updating,
\end{itemize}
\end{frame}

\questionslide

\end{document}
