\input{configpres} \title{Device Tree} \maketitle \subsection{Device Tree} \begin{frame} \frametitle{Overview} \tableofcontents \end{frame} \subsubsection{Background, Scope, Advantages} \begin{frame} \frametitle{Open Firmware (OF)} \begin{itemize} \item Designed by Sun in the late 1980's \item Popular on PowerPCs \item Similar to BIOS or EFI \item Integrated shell \item Supports FCode: platform/architecture independent bytecode \begin{itemize} \item Boot-time diagnostics \item Configuration code \item Device drivers \end{itemize} \item Defines a standard way to describe the hardware of a system (Device Tree) \end{itemize} \end{frame} \begin{frame} \frametitle{Device Tree (DT)} \begin{itemize} \item HW initialization was hardcoded in C before. \pause \item Derived from the device tree format used by OF. \pause \item DT spec \url{https://www.devicetree.org/specifications/} \pause \item DT spec is implemented by Linux, FreeBSD, zephyr, u-boot, Android. \pause \item Human readable source files: .dts and .dtsi \pause \item DT compile (dtc) transfers between source and binary (.dtb). \pause \item The binary format is called Flattened Device Tree (FDT). \pause \item FDT is passed to the kernel, kernel transfers it into an Expandable Device Tree (EDT). \pause \item EDT snippets are passed to 'matching' probe functions. \end{itemize} \end{frame} \begin{frame} \frametitle{DT and Linux} \begin{description} \item[1995] sparc/prom/tree.c written by David S. Miller \pause \item[2002] ppc64/kernel/prom.c Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner (IBM) \pause \item[2005] powerpc/kernel/prom.c copied from ppc64 to powerpc \pause \item[2007] Stephen Rothwell started to merge code from different architectures into drivers/of/base.c \pause \item[2009] 98 dts files in v2.6.30 (arm64, microblaze, powerpc) \pause \item[2011] Grant Likely added OF/DTS support for ARM (32-bit) \pause \item[2012] 165 dts files in v3.2 (+x86, +openrisc, +arm) \pause \item[2014] 648 dts files in v3.16 (+metag +c6x +arc +xtensa +mips) \pause \item[2016] 1199 dts files in v4.9 (+nios2 +h8300 +cris +sh) \pause \item[2018] 1612 dts files in v4.20 (-cris -metag +nds32) \pause \item[2019] there are still some board files (e.g. arch/arm/mach-omap1/board-nokia770.c) \end{description} \pause DTS as a part of OF became the quasi standard for describing non-iteratable hardware layouts to the Linux kernel. \end{frame} \begin{frame} \frametitle{DT Scope} Split HW specific information from Kernel binary \begin{itemize} \item The number and type of CPUs \item Base addresses and size of RAM \item Busses and bridges \item Peripheral device connections \item Interrupt controllers and IRQ line connections \item Pin multiplexing \end{itemize} \end{frame} \begin{frame} \frametitle{Advantages} \begin{itemize} \item Single kernel can support multiple SoCs \item Smaller amount of board support code \item Xilinx FPGA tools do DTS generation \item U-Boot can inspect and modify FDT before booting \end{itemize} \end{frame} \subsubsection{Syntax} \begin{frame}[fragile] \frametitle{The .dts Syntax} \includegraphics[width=8cm]{images/device-tree-syntax.jpg} \\ \tiny Source: http://wiki.dreamrunner.org \normalsize \\ \url{https://git.kernel.org/pub/scm/utils/dtc/dtc.git/plain/Documentation/dts-format.txt} \end{frame} \begin{frame}[fragile] \frametitle{Example .dts snippet} \begin{lstlisting} hello@1 { compatible = "virtual,hello", "virtio,hello"; index = <1>; }; \end{lstlisting} \begin{description} \item[Label] None defined \item[Node name] hello \item[Unit address] 1 \item[Property 1] \begin{description} \item[Name] compatible \item[Type] String Array \item[Value] ["virtual,hello", "virtio,hello"] \end{description} \item[Property 2] \begin{description} \item[Name] index \item[Type] 32bit Array including 1 Value \item[Value] 1 \end{description} \end{description} \end{frame} \begin{frame}[fragile] \frametitle{The compatible property} \begin{lstlisting} rtc@58 { compatible = "maxim,ds1338","microchip,mc1338"; }; \end{lstlisting} \begin{itemize} \item List of strings \item Needed for each node representing a device \item Used by the kernel to find a proper device driver \item First string is the Devicename: "," \item Next strings are compatible devices. \item Used to allow reuse of existing drivers, but allowing exact name. \item Use manufacturer names from Documentation/devicetree/bindings/vendor-prefixes.txt \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{Addressing} \begin{lstlisting} #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a9"; reg = <0>; }; \end{lstlisting} \begin{itemize} \item \#address-cells and \#size-cells in the parent node define the format of 'reg' of the child nodes. \item in the above example reg uses 1 address cell and no size cell \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{Overwrite values by label} \begin{lstlisting} $ cat mynode.dtsi / { soc { pic_3: pic@100 { reg = <0x100 0x20 >; }; }; }; \end{lstlisting} \begin{lstlisting} $ cat my.dts #include "my.dtsi" &pic_3 { reg = <0x200 0x30 >; }; \end{lstlisting} \begin{lstlisting} $ dtc -O dts my.dts /dts-v1/; / { soc { pic_3: pic@100 { reg = <0x200 0x30>; }; }; }; \end{lstlisting} \end{frame} \begin{frame} \frametitle{More complex example} If deeper understanding is required, \url{https://elinux.org/Device_Tree_Usage} is a good entry point. Also \url{https://elinux.org/Device_Tree_Linux} explains some Linux specific details. \end{frame} \begin{frame}[fragile] \frametitle{Bindings} The format of all device/bus specific properties are documented: Documentation/devicetree/bindings \end{frame} \subsubsection{Usage} \begin{frame}[fragile] \frametitle{Building a DT binary (FDT)} Out-of-tree with the Device Tree Compiler Build a FDT by given DTS \begin{lstlisting} $ dtc -o my.dtb my.dts \end{lstlisting} The Device Tree Compilier is also able to convert a FDT binary back to the human-readable DTS format: \begin{lstlisting} $ dtc -o my.dts my.dtb \end{lstlisting} OR in-tree using the Kernel Buildsystem (see next slide) \end{frame} \begin{frame}[fragile] \frametitle{Add a new ARM64 board platform to the kernel} \begin{lstlisting} diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -318,4 +318,9 @@ config ARCH_ZYNQMP +config ARCH_MYNEWBOARD + bool "My New Board" + help + This enables support for my new board + \end{lstlisting} \begin{lstlisting} diff --git a/arch/arm64/boot/dts/arm/Makefile b/arch/arm64/boot/dts/arm/Makefile --- a/arch/arm64/boot/dts/arm/Makefile +++ b/arch/arm64/boot/dts/arm/Makefile @@ -5,3 +5,4 @@ dtb-$(CONFIG_ARCH_VEXPRESS) += \ dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2f-1xv7-ca53x2.dtb +dtb-$(CONFIG_ARCH_MYNEWBOARD) += my-new.dtb \end{lstlisting} \begin{lstlisting} diff --git a/arch/arm64/boot/dts/arm/my-new.dts b/arch/arm64/boot/dts/arm/my-new.dts new file mode 100644 --- /dev/null +++ b/arch/arm64/boot/dts/arm/my-new.dts @@ -0,0 +1,16 @@ +/* ARM64 MY BOARD Platform [..] \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Passing FDT to the kernel} \begin{itemize} \item statically linked into the kernel \begin{lstlisting} # CONFIG_ARM_APPENDED_DTB and CONFIG_ARM_ATAG_DTB_COMPAT # needs to be enabled $ cat arch/arm64/boot/Image arch/arm64/boot/dts/arm/my.dtb \ >> arch/arm/boot/zImage-dtb \end{lstlisting} \pause \item passed to the kernel at boot time\\ The bootloader passes the DTB address through r2 \begin{description} \item[U-Boot command] boot[mz] - \item[Barebox variables] bootm.image, bootm.oftree \end{description} \pause \item FIT image - topic for its own, more details in this presentation:\\ \url{https://elinux.org/images/f/f4/Elc2013_Fernandes.pdf} \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{QEMU and FDT} Dump dtb \begin{lstlisting} $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -m 512 -smp 2 \ -device virtio-net-device,netdev=unet \ -device ivshmem-doorbell,vectors=1,chardev=chid1 \ -chardev socket,path=/tmp/ivshmem_socket,id=chid1 \ -kernel /scratch/linux-arm64/arch/arm64/boot/Image \ -append "console=ttyAMA0 ip=dhcp root=/dev/nfs nfsroot=10.0.2.2:/nfs/debian,nfsvers=3 rw" \ -machine dumpdtb=my.dtb \end{lstlisting} Virtio and ivshmem devices are iteratable. So they are not dumped into the dtb. Starting Qemu with a dtb: \begin{lstlisting} $ qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic \ -device virtio-net-device,netdev=unet \ -device ivshmem-doorbell,vectors=1,chardev=chid1 \ -chardev socket,path=/tmp/ivshmem_socket,id=chid1 \ -kernel /scratch/linux-arm64/arch/arm64/boot/Image \ -dtb /tmp/my.dtb \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Match a driver and DT node} DTS snippet \begin{lstlisting} hello@1 { compatible = "virtual,hello"; }; \end{lstlisting} driver source \begin{lstlisting} static int hello_probe(struct platform_device *pdev) { return 0; } static const struct of_device_id hello_match[] = { { .compatible = "virtual,hello", }, { } }; static struct platform_driver hello_driver = { .driver = { .name = "hello", .owner = THIS_MODULE, .of_match_table = hello_match, }, .probe = hello_probe, }; static int __init hello_init(void) { return platform_driver_register(&hello_driver); } module_init(hello_init); \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Reading properties in drivers} DTS snippet \begin{lstlisting} hello@1 { compatible = "virtual,hello"; index = <1>; }; \end{lstlisting} driver source \begin{lstlisting} static int hello_probe(struct platform_device *pdev) { int ret; u32 value; ret = of_property_read_u32(pdev->dev.of_node, "index", &value); if (ret < 0) pr_err("no index specified\n"); return ret; } \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Reading DT/OF properties from userspace} \begin{lstlisting} $ tree /proc/device-tree $ cat /proc/device-tree/compatible OR $ tree /sys/firmware/devicetree/base $ cat /sys/firmware/devicetree/base/compatible \end{lstlisting} rebuilding DT from user-space: \begin{lstlisting} $ dtc -I fs /sys/firmware/devicetree/base -o my.dts $ dtc -I fs /sys/firmware/devicetree/base -o my.dtb \end{lstlisting} \end{frame} \subsubsection{Linux internals} \begin{frame} \frametitle{Source locations} \begin{description} \item[DTS files] arch/*/boot/dts/* \item[Kernel framework] drivers/of/* \item[Userspace components] 'scripts/dtc' are imported from \url{https://git.kernel.org/cgit/utils/dtc/dtc.git} \item[Docs] Documentation/devicetree \end{description} \end{frame} \subsubsection{Future} \begin{frame} \frametitle{Current development} Device Tree Overlays \begin{itemize} \item Needed to support shields. \item Supported by u-boot, but currently not by the kernel. \item Status Plumbers Conference '18 \url{https://elinux.org/images/8/86/Overlay_frank.pdf} \end{itemize} Device Tree Schema \begin{itemize} \item \url{https://elinux.org/images/6/67/Hkg18-120-devicetreeschema-grantlikely-180404144834.pdf} \end{itemize} \end{frame} \begin{frame} \frametitle{Further information} \begin{itemize} \item elinux.org Device Tree wiki\\ \url{https://elinux.org/Device_Tree_Reference} \item Thomas Petazzoni - Device Tree for Dummies | ELC 2014\\ \url{https://www.youtube.com/watch?v=uzBwHFjJ0vU}\\ \url{https://elinux.org/images/f/f9/Petazzoni-device-tree-dummies_0.pdf} \item \url{https://elinux.org/Device_tree_future} \item Read 'Documentation/devicetree/bindings/submitting-patches.txt' if you consider sending sth. related to DT upstream. \end{itemize} \end{frame} \begin{frame} \frametitle{Excercise} \begin{itemize} \item Extend the hello module to become a platform driver including a of match table. \item Dump the DTB via Qemu and convert it to DTS format. \item Add a proper node to the DTS to trigger the probe function of the hello module. \item Convert the DTS to DTB format and use it to start Qemu. \item Check that the hello module is loaded automatically and its probe function was called. \item Add multiple hello nodes to the Device Tree. Check how often the init and probe functions are called. \item Extend the hello module to read a property from the hello DTS node. \item Add this property to the DTS and check if it's read. \end{itemize} \end{frame} \input{tailpres}