|
| 1 | +========================= |
| 2 | +Kernel Development on ARM |
| 3 | +========================= |
| 4 | + |
| 5 | +Lab objectives |
| 6 | +============== |
| 7 | + |
| 8 | +* get a feeling of what System on a Chip (SoC) means |
| 9 | +* get familiar with embedded world using ARM as support architecture |
| 10 | +* understand what a Board Support Package means (BSP) |
| 11 | +* compile and boot an ARM kernel with Qemu using i.MX6UL platform as an example |
| 12 | +* get familiar with hardware description using Device Trees |
| 13 | + |
| 14 | +System on a Chip |
| 15 | +================ |
| 16 | + |
| 17 | +A System on a Chip (**SoC**) is an integrated circuit (**IC**) that integrates an entire system onto it. The components |
| 18 | +that can be usually found on an SoC include a central processing unit (**CPU**), memory, input/output ports, storage devices |
| 19 | +together with more sophisticated modules like audio digital interfaces, neural processing units (**NPU**) or graphical |
| 20 | +processing units (**GPU**). |
| 21 | + |
| 22 | +SoCs can be used in various applications most common are: |
| 23 | + - consumer electronics (TV sets, mobile phones, video game consoles) |
| 24 | + - industrial computers (medical imaging, etc) |
| 25 | + - automotive |
| 26 | + - home appliances |
| 27 | + |
| 28 | +The leading architecture for SoCs is **ARM**. Worth mentioning here is that there are also x86-based SoCs platforms. Another thing |
| 29 | +we need to keep an eye on is **RISC-V** an open standard instruction set architecture. |
| 30 | + |
| 31 | +A simplified view of an **ARM** platform is shown in the image below: |
| 32 | + |
| 33 | +.. image:: ../res/schematic.png |
| 34 | + :align: center |
| 35 | + |
| 36 | +We will refer as a reference platform at NXP's `i.MX6UL <imx6ul>`_ platform, but in general all SoC's contain the following building blocks: |
| 37 | + |
| 38 | + - one or more CPU cores |
| 39 | + - a system bus |
| 40 | + - clock and reset module |
| 41 | + |
| 42 | + - PLL |
| 43 | + - OSC |
| 44 | + - reset controller |
| 45 | + |
| 46 | + - interrupt controller |
| 47 | + - timers |
| 48 | + - memory controller |
| 49 | + - peripheral controllers |
| 50 | + |
| 51 | + - `I2C <https://en.wikipedia.org/wiki/I%C2%B2C>`_ |
| 52 | + - `SPI <https://en.wikipedia.org/wiki/Serial_Peripheral_Interface>`_ |
| 53 | + - `GPIO <https://en.wikipedia.org/wiki/General-purpose_input/output>`_ |
| 54 | + - `Ethernet <https://en.wikipedia.org/wiki/Network_interface_controller>`_ (for network) |
| 55 | + - `uSDHC <https://en.wikipedia.org/wiki/MultiMediaCard>`_ (for storage) |
| 56 | + - USB |
| 57 | + - `UART <https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter>`_ |
| 58 | + - `I2S <https://en.wikipedia.org/wiki/I%C2%B2S>`_ (for sound) |
| 59 | + - eLCDIF (for LCD Panel) |
| 60 | + |
| 61 | +Here is the complete block diagram for i.MX6UL platform: |
| 62 | + |
| 63 | +.. image:: https://www.nxp.com/assets/images/en/block-diagrams/IMX6UL-BD.jpg |
| 64 | + :alt: IMX6UL-BD |
| 65 | + :width: 60 % |
| 66 | + :align: center |
| 67 | + |
| 68 | +i.MX6UL Evaluation Kit board looks like this: |
| 69 | + |
| 70 | +.. image:: https://www.compulab.com/wp-content/gallery/sbc-imx6ul/compulab_sbc-imx6ul_single-board-computer.jpg |
| 71 | + :alt: imx6ul-evk |
| 72 | + :width: 60 % |
| 73 | + :align: center |
| 74 | + |
| 75 | +Other popular SoC boards: |
| 76 | + |
| 77 | + * `Broadcom Raspberry Pi <https://en.wikipedia.org/wiki/Raspberry_Pi>`_ |
| 78 | + * `Texas Instruments Beagle board <https://en.wikipedia.org/wiki/BeagleBoard>`_ |
| 79 | + * `Odroid Xu4 <https://wiki.odroid.com/odroid-xu4/odroid-xu4>`_ |
| 80 | + * `Nvidia Jetson Nano <https://developer.nvidia.com/embedded/jetson-nano-developer-kit>`_ |
| 81 | + |
| 82 | +Board Support package |
| 83 | +===================== |
| 84 | + |
| 85 | +A board support package (**BSP**) is the minimal set of software packages that allow to demonstrate the capabilities of a certain hardware platform. This includes: |
| 86 | + |
| 87 | + - toolchain |
| 88 | + - bootloader |
| 89 | + - Linux kernel image, device tree files and drivers |
| 90 | + - root filesystem |
| 91 | + |
| 92 | +Semiconductor manufacturers usually provide a **BSP** together with an evaluation board. BSP is typically bundled using `Yocto <https://www.yoctoproject.org/>`_ |
| 93 | + |
| 94 | +Toolchain |
| 95 | +========= |
| 96 | +Because our development machines are mostly x86-based we need a cross compiler that can produce executable |
| 97 | +code for ARM platform. |
| 98 | + |
| 99 | +We can build our own cross compiler from scratch using https://crosstool-ng.github.io/ or we can install one |
| 100 | + |
| 101 | +.. code-block:: bash |
| 102 | +
|
| 103 | + $ sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf # for arm32 |
| 104 | + $ sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu # for arm64 |
| 105 | +
|
| 106 | +There are several of toolchain binaries depending on the configuration: |
| 107 | + |
| 108 | + - With "arm-eabi-gcc" you have the Linux system C library which will make calls into the kernel IOCTLs, e.g. for allocating memory pages to the process. |
| 109 | + - With "arm-eabi-none-gcc" you are running on platform which doesn't have an operating system at all - so the C library is different to cope with that. |
| 110 | + |
| 111 | +Compiling the Linux kernel on ARM |
| 112 | +--------------------------------- |
| 113 | + |
| 114 | +Compile the kernel for 32bit ARM boards: |
| 115 | + |
| 116 | +.. code-block:: bash |
| 117 | +
|
| 118 | + # select defconfig based on your platform |
| 119 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make imx_v6_v7_defconfig |
| 120 | + # compile the kernel |
| 121 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make -j8 |
| 122 | +
|
| 123 | +Compile the kernel for 64bit ARM boards: |
| 124 | + |
| 125 | +.. code-block:: bash |
| 126 | +
|
| 127 | + # for 64bit ARM there is a single config for all supported boards |
| 128 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make defconfig |
| 129 | + # compile the kernel |
| 130 | + $ ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j8 |
| 131 | +
|
| 132 | +Linux kernel image |
| 133 | +================== |
| 134 | + |
| 135 | +The kernel image binary is named ``vmlinux`` and it can be found in the root of the kernel tree. Compressed image used for booting can be found under: |
| 136 | + |
| 137 | +- ``arch/arm/boot/Image``, for arm32 |
| 138 | +- ``arch/arm64/boot/Image``, for arm64 |
| 139 | + |
| 140 | +.. code-block:: bash |
| 141 | +
|
| 142 | + $ file vmlinux |
| 143 | + vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped |
| 144 | +
|
| 145 | + $ file vmlinux |
| 146 | + vmlinux: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), statically linked, not stripped |
| 147 | +
|
| 148 | +Rootfs |
| 149 | +====== |
| 150 | + |
| 151 | +The root filesystem (``rootfs``) is the filesystem mounted at the top of files hierarchy (``/``). It should contain at least |
| 152 | +the critical files allowing the system to boot to a shell. |
| 153 | + |
| 154 | +.. code-block:: bash |
| 155 | +
|
| 156 | + root@so2$ tree -d -L 2 |
| 157 | + ├── bin |
| 158 | + ├── boot |
| 159 | + ├── dev |
| 160 | + ├── etc |
| 161 | + ├── home |
| 162 | + │ └── root |
| 163 | + ├── lib |
| 164 | + │ └── udev |
| 165 | + ├── mnt |
| 166 | + ├── proc |
| 167 | + ├── sbin |
| 168 | + │ └── init |
| 169 | + ├── sys |
| 170 | + ├── usr |
| 171 | + │ ├── bin |
| 172 | + │ ├── include |
| 173 | + │ ├── lib |
| 174 | + └── var |
| 175 | +
|
| 176 | +As for ``x86`` we will make use of Yocto rootfs images. In order to download an ``ext4`` rootfs image for ``arm32`` one needs to run: |
| 177 | + |
| 178 | +.. code-block:: bash |
| 179 | +
|
| 180 | + $ cd tools/labs/ |
| 181 | + $ ARCH=arm make core-image-minimal-qemuarm.ext4 |
| 182 | +
|
| 183 | +Device tree |
| 184 | +=========== |
| 185 | + |
| 186 | +Device tree (**DT**) is a tree structure used to describe the hardware devices in a system. Each node in the tree describes a device hence it is called **device node**. DT was introduced |
| 187 | +to provide a way to discover non-discoverable hardware (e.g a device on an I2C bus). This information was previously stored inside the source code for the Linux kernel. This meant that |
| 188 | +each time we needed to modify a node for a device the kernel needed to be recompiled. This no longer holds true as device tree and kernel image are separate binaries now. |
| 189 | + |
| 190 | +Device trees are stored inside device tree sources (*.dts*) and compiled into device tree blobs (*.dtb*). |
| 191 | + |
| 192 | +.. code-block:: bash |
| 193 | +
|
| 194 | + # compile dtbs |
| 195 | + $ make dtbs |
| 196 | +
|
| 197 | + # location for DT sources on arm32 |
| 198 | + $ ls arch/arm/boot/dts/ |
| 199 | + imx6ul-14x14-evk.dtb imx6ull-14x14-evk.dtb bcm2835-rpi-a-plus.dts |
| 200 | +
|
| 201 | + # location for DT source on arm64 |
| 202 | + $ ls arch/arm64/boot/dts/<vendor> |
| 203 | + imx8mm-evk.dts imx8mp-evk.dts |
| 204 | +
|
| 205 | +The following image is a represantation of a simple device tree, describing board type, cpu and memory. |
| 206 | + |
| 207 | +.. image:: ../res/dts_node.png |
| 208 | + :align: center |
| 209 | + |
| 210 | +Notice that a device tree node can be defined using ``label: name@address``: |
| 211 | + |
| 212 | + - ``label``, is an identifier used to reference the node from other places |
| 213 | + - ``name``, node identifier |
| 214 | + - ``address``, used to differentiate nodes with the same name. |
| 215 | + |
| 216 | +A node might contain several properties arranged in the ``name = value`` format. The name is a string |
| 217 | +and the value can be bytes, strings, array of strings. |
| 218 | + |
| 219 | +Here is an example: |
| 220 | + |
| 221 | +.. code:: c |
| 222 | +
|
| 223 | + / { |
| 224 | + node@0 { |
| 225 | + empty-property; |
| 226 | + string-property = "string value"; |
| 227 | + string-list-property = "string value 1", "string value 2"; |
| 228 | + int-list-property = <value1 value2>; |
| 229 | +
|
| 230 | + child-node@0 { |
| 231 | + child-empty-property; |
| 232 | + child-string-property = "string value"; |
| 233 | + child-node-reference = <&child-node1>; |
| 234 | + }; |
| 235 | +
|
| 236 | + child-node1: child-node@1 { |
| 237 | + child-empty-property; |
| 238 | + child-string-property = "string value"; |
| 239 | + }; |
| 240 | + }; |
| 241 | + }; |
| 242 | +
|
| 243 | +Qemu |
| 244 | +==== |
| 245 | + |
| 246 | +We will use ``qemu-system-arm`` to boot 32bit ARM platforms. Although, this can be installed from official distro repos, for example: |
| 247 | + |
| 248 | +.. code:: bash |
| 249 | +
|
| 250 | + sudo apt-get install -y qemu-system-arm |
| 251 | +
|
| 252 | +We strongly recommend using latest version of ``qemu-system-arm`` build from sources: |
| 253 | + |
| 254 | +.. code:: bash |
| 255 | +
|
| 256 | + $ git clone https://gitlab.com/qemu-project/qemu.git |
| 257 | + $ ./configure --target-list=arm-softmmu --disable-docs |
| 258 | + $ make -j8 |
| 259 | + $ ./build/qemu-system-arm |
| 260 | +
|
| 261 | +Exercises |
| 262 | +========= |
| 263 | + |
| 264 | +.. include:: ../labs/exercises-summary.hrst |
| 265 | +.. |LAB_NAME| replace:: arm_kernel_development |
| 266 | + |
| 267 | +.. warning:: |
| 268 | + |
| 269 | + The rules for working with the virtual machine for ``ARM`` are modified as follows |
| 270 | + |
| 271 | + .. code-block:: shell |
| 272 | +
|
| 273 | + # modules build |
| 274 | + tools/labs $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make build |
| 275 | + # modules copy |
| 276 | + tools/labs $ ARCH=arm make copy |
| 277 | + # kernel build |
| 278 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make -j8 |
| 279 | +
|
| 280 | +0. Intro |
| 281 | +-------- |
| 282 | + |
| 283 | +Inspect the following locations in the Linux kernel code and identify platforms and vendors using |
| 284 | +ARM architecture: |
| 285 | + |
| 286 | + - 32-bit: ``arch/arm/boot/dts`` |
| 287 | + - 64-bit: ``arch/arm64/boot/dts`` |
| 288 | + |
| 289 | + |
| 290 | +Use ``qemu`` and look at the supported platforms: |
| 291 | + |
| 292 | +.. code-block:: bash |
| 293 | +
|
| 294 | + ../qemu/build/arm-softmmu/qemu-system-arm -M ? |
| 295 | +
|
| 296 | +.. note:: We used our own compiled version of ``Qemu`` for ``arm32``. See `Qemu`_ section for more details. |
| 297 | + |
| 298 | +1. Boot |
| 299 | +------- |
| 300 | + |
| 301 | +Use ``qemu`` to boot ``i.MX6UL`` platform. In order to boot, we first need to compile the kernel. |
| 302 | +Review `Compiling the Linux kernel on ARM`_ section. |
| 303 | + |
| 304 | +Successful compilation will result in the following binaries: |
| 305 | + |
| 306 | + - ``arch/arm/boot/Image``, kernel image compiled for ARM |
| 307 | + - ``arch/arm/boot/dts/imx6ul-14x14-evk.dtb``, device tree blob for ``i.MX6UL`` board |
| 308 | + |
| 309 | +Review `Rootfs`_ section and download ``core-image-minimal-qemuarm.ext4`` rootfs. |
| 310 | +Run ``qemu`` using then following command: |
| 311 | + |
| 312 | +.. code-block:: bash |
| 313 | +
|
| 314 | + ../qemu/build/arm-softmmu/qemu-system-arm -M mcimx6ul-evk -cpu cortex-a7 -m 512M \ |
| 315 | + -kernel arch/arm/boot/zImage -nographic -dtb arch/arm/boot/dts/imx6ul-14x14-evk.dtb \ |
| 316 | + -append "root=/dev/mmcblk0 rw console=ttymxc0 loglevel=8 earlycon printk" -sd tools/labs/core-image-minimal-qemuarm.ext4 |
| 317 | +
|
| 318 | +.. note:: LCDIF and ASRC devices are not well supported with ``Qemu``. Remove them from compilation. |
| 319 | + |
| 320 | +.. code-block:: bash |
| 321 | +
|
| 322 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig |
| 323 | + # set FSL_ASRC=n and DRM_MXSFB=n |
| 324 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make -j8 |
| 325 | +
|
| 326 | +Once the kernel is booted check kernel version and cpu info: |
| 327 | + |
| 328 | +.. code-block:: bash |
| 329 | +
|
| 330 | + $ cat /proc/cpuinfo |
| 331 | + $ cat /proc/version |
| 332 | +
|
| 333 | +2. CPU information |
| 334 | +------------------ |
| 335 | + |
| 336 | +Inspect the CPU configuration for ``NXP i.MX6UL`` board. Start with ``arch/arm/boot/dts/imx6ul-14x14-evk.dts``. |
| 337 | + |
| 338 | + - find ``cpu@0`` device tree node and look for ``operating-points`` property. |
| 339 | + - read the maximum and minimum operating frequency the processor can run |
| 340 | + |
| 341 | + .. code:: bash |
| 342 | +
|
| 343 | + $ cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq |
| 344 | + $ cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq |
| 345 | +
|
| 346 | +3. I/O memory |
| 347 | +------------- |
| 348 | +Inspect I/O space configuration for ``NXP i.MX6UL`` board. Start with ``arch/arm/boot/dts/imx6ul-14x14-evk.dts`` and identify each device mentioned below. |
| 349 | + |
| 350 | +.. code:: bash |
| 351 | +
|
| 352 | + $ cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq |
| 353 | + 00900000-0091ffff : 900000.sram sram@900000 |
| 354 | + 0209c000-0209ffff : 209c000.gpio gpio@209c000 |
| 355 | + 021a0000-021a3fff : 21a0000.i2c i2c@21a0000 |
| 356 | + 80000000-9fffffff : System RAM |
| 357 | +
|
| 358 | +Identify device tree nodes corresponding to: |
| 359 | + |
| 360 | + - ``System RAM``, look for ``memory@80000000`` node in ``arch/arm/boot/dts/imx6ul-14x14-evk.dtsi``. What's the size of the System RAM? |
| 361 | + - ``GPIO1``, look for ``gpio@209c000`` node in ``arch/arm/boot/dts/imx6ul.dtsi``. What's the size of the I/O space for this device? |
| 362 | + - ``I2C1``, look for ``i2c@21a0000`` node in ``arch/arm/boot/dts/imx6ul.dtsi``. What's the size of the I/O spaces for this device? |
| 363 | + |
| 364 | +4. Hello World |
| 365 | +-------------- |
| 366 | + |
| 367 | +Implement a simple kernel module that prints a message at load/unload time. Compile it and load it on ``i.MX6UL`` emulated platform. |
| 368 | + |
| 369 | +.. code-block:: shell |
| 370 | +
|
| 371 | + # modules build |
| 372 | + tools/labs $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make build |
| 373 | + # modules copy |
| 374 | + tools/labs $ ARCH=arm make copy |
| 375 | + # kernel build |
| 376 | + $ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make -j8 |
| 377 | +
|
| 378 | +5. Simple device |
| 379 | +---------------- |
| 380 | + |
| 381 | +Implement a driver for a simple platform device. Find ``TODO 1`` and notice how ``simple_driver`` is declared and register as a platform driver. |
| 382 | +Follow ``TODO 2`` and add the ``so2,simple-device-v1`` and ``so2,simple-device-v2`` compatible strings in the simple_device_ids array. |
| 383 | + |
| 384 | +Create two device tree nodes in ``arch/arm/boot/dts/imx6ul.dtsi`` under ``soc`` node with compatible strings ``so2,simple-device-v1`` and |
| 385 | +``so2,simple-device-v2`` respectively. Then notice the behavior when loading ``simple_driver`` module. |
| 386 | + |
| 387 | +.. _imx6ul: https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/i-mx-applications-processors/i-mx-6-processors/i-mx-6ultralite-processor-low-power-secure-arm-cortex-a7-core:i.MX6UL |
0 commit comments