Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications

After showing how to build a minimal Linux system for the STM32MP157 platform, and how to connect and use an I2C based pressure/temperature/humidity sensor, we are now going to enable Qt5 and run some example graphical Qt5 applications. This is a necessary requirement before developing our own Qt5 application, which will be the topic of the next article in this series.

List of articles in this series:

  1. Building a Linux system for the STM32MP1: basic system
  2. Building a Linux system for the STM32MP1: connecting an I2C sensor
  3. Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications
  4. Building a Linux system for the STM32MP1: setting up a Qt5 application development environment
  5. Building a Linux system for the STM32MP1: developing a Qt5 graphical application
  6. Building a Linux system for the STM32MP1: implementing factory flashing
  7. Building a Linux system for the STM32MP1: remote firmware updates

Display support in the Linux kernel

First of all, it is very important to distinguish the display controller from the GPU, as many people getting started with embedded systems and Linux tend to confuse both, while they are completely different things, both in terms of functionality and software support.

A display controller is an hardware block that takes the content of a memory area containing values representing the color of all pixels on your screen (a framebuffer) and sends it out to a display panel over hardware interfaces such as HDMI, DisplayPort, parallel interfaces or MIPI DSI. Some display controllers can take several framebuffers as input and compose them, potentially rescaling the size of some of them, and overlaying them with transparency. For example, some display controllers can take a framebuffer containing video frames, overlay another framebuffer that contains transparent pixels (to see the video) and some other pixels showing the UI of a video player, and another framebuffer overlayed on top of the two other ones that contains the mouse cursor. There are no 3D rendering or OpenGL operations involved: a display controller’s job is purely about taking framebuffer(s) contents and showing them on a display.

A GPU (or Graphics Processing Unit) is a very flexible programmable hardware unit, which among other things can be used to implement the OpenGL 3D rendering API, but also other APIs such as OpenCL. OpenGL and a GPU allow to offload in hardware the complex calculations that need to be done to render a 3D scene into a framebuffer of pixels. But a GPU by itself is not responsible at all for displaying on a screen what was rendered: it is the responsibility of a display controller.

Some system-on-chips only have a display controller, and no GPU at all, so you can display contents on one or several display panels, but you cannot benefit from hardware acceleration for 3D rendering. There are also use-cases where GPUs are used, but never to render anything looking like a 3D scene: that is for example the use-case for the OpenCL API, which allows to leverage the processing power of GPUs for general purpose programming.

In Linux, both display controllers and GPUs are managed by a Linux kernel subsystem called DRM, for Direct Rendering Manager. The DRM drivers are located in drivers/gpu/drm in the Linux kernel source code. If the hardware also has a GPU in addition to the display controller, then the most significant part of handling the GPU is done in user-space libraries implementing OpenGL. In the open-source world, the de-facto standard OpenGL implementation is Mesa3D, which has support for a number of different GPUs. From a GPU driver perspective, the kernel mainly serves as a way for the user-space OpenGL library to allocate buffers and send commands to the GPU.

The STM32MP15 has both a display controller and a GPU, but in this blog post, we are only going to make use of the display controller. The Linux kernel DRM driver used for the display controller of the STM32MP15 is in drivers/gpu/drm/stm. The driver can be enabled using the CONFIG_DRM_STM kernel configuration option.

In addition, the display panel used on the STM32MP15 Discovery Kit is connected to the SoC using the MIPI DSI interface. The STM32MP15 SoC contains a DSI encoder hardware block from Synopsys, and a glue driver for the DSI encoder is available in drivers/gpu/drm/stm/dw_mipi_dsi-stm.c and can be enabled using the CONFIG_DRM_STM_DSI option.

It turns out that the kernel configuration file we’re using since our initial blog post does have both of these options enabled, as well as the option providing support for the specific DSI display panel used on the Discovery board:

CONFIG_DRM=y
CONFIG_DRM_STM=y
CONFIG_DRM_STM_DSI=y
CONFIG_DRM_PANEL_ORISETECH_OTM8009A=y

The user-space interface of DRM is detailed in its documentation. Typical applications do not use directly this interface, and instead rely on a display stack in user-space such as X.org or Wayland, or directly on a graphical toolkit like Qt.

In addition, the DRM subsystem implements a compatibility layer that emulates the Linux framebuffer user-space interface, as documented in framebuffer.txt. This allows to support older applications/libraries that don’t use DRM directly.

In our case, we are going to use the Qt graphical toolkit, and on embedded Linux systems, it has four main display backends: eglfs (which requires an OpenGL/EGL graphics stack), linuxfb (which uses a simple legacy framebuffer interface), wayland (for Wayland, obviously) and xcb (for X.org). To keep things simple for this series and blog post, and because we don’t require OpenGL support, we will use the linuxfb backend.

Touch panel support in the Linux kernel

The Linux kernel has a subsystem called input for all input devices, such as keyboards, mice, touchscreens, joysticks and more. Its user-space interface is described in details in the kernel documentation, and this interface is used by most display stacks in user-space. For example, Qt supports it through the backend called evdev.

In terms of hardware, the Discovery Kit display panel integrates a touch panel that uses a Focaltech FT6236 controller, connected over I2C. The Device Tree describes this touch panel device as follows:

&i2c1 {
        touchscreen@2a {
                compatible = "focaltech,ft6236";
                reg = <0x2a>;
                interrupts = <2 2>;
                interrupt-parent = <&gpiof>;
                interrupt-controller;
                touchscreen-size-x = <480>;
                touchscreen-size-y = <800>;
                status = "okay";
        };
};

The corresponding driver in the Linux kernel is drivers/input/touchscreen/edt-ft5x06.c, which can be enabled using the CONFIG_TOUCHSCREEN_EDT_FT5X06 kernel configuration option.

The input subsystem, its evdev interface, and the specific driver for our touch panel are all already enabled in the kernel configuration file we are using since our first blog post:

CONFIG_INPUT_EVDEV=y
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_EDT_FT5X06=y

Basic testing of the display and touch panel

To test the touch panel and display, we can start with very simple tools instead of using directly a complex graphical stack. The tools we recommend to use are:

  • The evtest program from the project of the same name. It allows to dump input events from any input device.
  • The modetest program that comes from the libdrm project. It allows to test a display by showing some pre-defined pictures.

Let’s add those two software components in our Buildroot configuration: go to menuconfig, and enable the BR2_PACKAGE_EVTEST, BR2_PACKAGE_LIBDRM and BR2_PACKAGE_LIBDRM_INSTALL_TESTS options. Restart the build of the Buildroot system by running make, and once the build has completed, write the new SD card image to your SD card.

On the system, let’s have a look at available input device (output edited to fit in the blog post):

# ls -l /sys/class/input/
event0 -> ...platform/soc/40012000.i2c/i2c-0/0-002a/input/input0/event0
event1 -> ...platform/soc/5c002000.i2c/i2c-2/2-0033/5c002000.i2c:stpmic@33:onkey/input/input2/event1
input0 -> ...platform/soc/40012000.i2c/i2c-0/0-002a/input/input0
input2 -> ...platform/soc/5c002000.i2c/i2c-2/2-0033/5c002000.i2c:stpmic@33:onkey/input/input2

So input0 is our touch panel (connected on I2C bus 0, at address 0x2A), and event0 is its evdev user-space interface. This evdev interface is accessible through the /dev/input/event0 device file:

# ls -l /dev/input/event0 
crw-------    1 root     root       13,  64 Jan  1  1970 /dev/input/event0

Now we can run evtest. With no arguments, it shows the list of available input devices, their name, and allows use to chose the one we would like to test:

# evtest 
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:	generic ft5x06 (11)
/dev/input/event1:	pmic_onkey
Select the device event number [0-1]: 0

Once evtest is running, press the touch panel, and you will see events reported like this:

Event: time 946685148.736402, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 0
Event: time 946685148.736402, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 259
Event: time 946685148.736402, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 428
Event: time 946685148.736402, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
Event: time 946685148.736402, type 3 (EV_ABS), code 0 (ABS_X), value 259
Event: time 946685148.736402, type 3 (EV_ABS), code 1 (ABS_Y), value 428

These events show the coordinates of the press, and the actual press event (BTN_TOUCH).

With the touch panel working, let’s move on to the display. In fact, you know the display is already working: the fbcon kernel driver provides a console over the framebuffer, which is why you are seeing the Linux kernel messages on the screen. Nevertheless, let’s use the modetest program from libdrm. Without any argument, it just prints out some details about the available display hardware. First the encoders:

Encoders:
id	crtc	type	possible crtcs	possible clones	
28	0	DPI	0x00000001	0x00000000
30	33	DSI	0x00000001	0x00000000

So the display controller has two encoders: one with a DPI interface (i.e parallel RGB interface) and one with a (MIPI) DSI interface.

Then, we have the list of connectors:

Connectors:
id	encoder	status		name		size (mm)	modes	encoders
29	0	disconnected	HDMI-A-1       	0x0		0	28
[..]
31	30	connected	DSI-1          	52x86		1	30
  modes:
	name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
  480x800 50 480 578 610 708 800 815 825 839 29700 flags: ; type: preferred, driver

We have a HDMI connector, which can be used with the encoder of id 28, that is the DPI encoder. Indeed, the RGB parallel interface of the STM32 processor is fed on the board into an HDMI transceiver, that goes out with HDMI signals on connector CN9. So from the point of view of the SoC, it is a parallel RGB interface, but thanks to the HDMI transceiver on the Discovery board, it is in fact usable as an HDMI connector.

The second connector is the DSI connector, which can be used with encoder of id 30, i.e the DSI encoder, which makes sense.

So let’s ask modetest to display its test picture on the DSI connector, which has id 31. The DSI panel resolution is 480×800, so we’ll use the following command:

# modetest -s 31:480x800

And voilà:

modetest on the STM32MP15 Discovery board

Now if we plug an HDMI screen to the HDMI connector, the modetest output about connector 29 changes as the connector is no longer disconnected:

Connectors:
id	encoder	status		name		size (mm)	modes	encoders
29	0	connected	HDMI-A-1       	520x290		11	28
  modes:
	name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
  1280x720 60 1280 1390 1430 1650 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  1280x720 60 1280 1390 1430 1650 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  1280x720 50 1280 1720 1760 1980 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  [...]

So we can ask modetest to display a 1280×720 picture on the HDMI screen:

# modetest -s 29:1280x720

Enabling Qt5 support in Buildroot

Now that we have successfully tested the display and touchscreen, it is time to move on and use a powerful graphical toolkit for embedded Linux systems: Qt. Qt is already packaged in Buildroot, and therefore very easy to add to our system, including some examples. Simply enable the following options in your Buildroot configuration:

  • BR2_PACKAGE_QT5, which enables Qt as a whole, and automatically selects the core Qt module called qt5base
  • BR2_PACKAGE_QT5BASE_GUI, which enables GUI support in qt5base. The linuxfb backend is automatically selected, but provided the appropriate dependencies are enabled, other display backends can be enabled as well. In our case, we’ll use the linuxfb backend so the default selection will work for us.
  • BR2_PACKAGE_QT5BASE_WIDGETS, which enables the Qt5 Widget library, which allows to easily write graphical applications with buttons, text boxes, drop down lists and other familiar graphical widgets
  • BR2_PACKAGE_QT5BASE_EXAMPLES, to enable the example Qt5 applications
  • BR2_PACKAGE_QT5BASE_FONTCONFIG, to enable the fontconfig support in Qt. This allows Qt to discover the fonts available on our system to render text.
  • BR2_PACKAGE_DEJAVU, which will provide one font to render text. Without this, Qt applications would run, but no text would be rendered

Once these options are enabled, restart the build with make and rewrite the new image to your SD card.

Test Qt5 application

All the Qt examples are installed in /usr/lib/qt/examples/, so you can try all of them. Let’s start with the analogclock for example:

# /usr/lib/qt/examples/gui/analogclock/analogclock -platform linuxfb

Qt Analog Clock

Then, another one which allows to test the touch panel:

# /usr/lib/qt/examples/widgets/scroller/graphicsview/graphicsview -platform linuxfb

Qt Graphics View

And a more “complete” and useful application, which as you can see does not fit very well on a 480×800 screen in portrait mode, but also allows to use the touchscreen:

# /usr/lib/qt/examples/widgets/widgets/calculator/calculator -platform linuxfb

Qt Calculator

Filesystem size

To conclude this article, let’s have a look at the size of our filesystem size. After a completely clean build (make clean all), Buildroot can generate a nice graph of the filesystem size using make graph-size. In our case, it generates the following graph:

Filesystem size graph

So the overall filesystem size is 58.7 MB, on which 34.4 MB come from Qt. But our Qt is compiled with examples, and the examples take up 16.7 MB (this is not visible on the graph, it was calculated by looking at the size of /usr/lib/qt/examples/) on the target). Also, the dejavu font package is quite large, with 9.6 MB.

Conclusion

You can find the exact Buildroot source code used to reproduce the system used in this article in the branch at 2019.02/stm32mp157-dk-blog-3.

In this article, we learned about how Linux manages display and input devices and how to test them with simple applications such as modetest and evtest. Then we looked at how to add the Qt library to our Buildroot configuration, and verify it is working using Qt example applications.

Our next blog post will cover how to build a real Qt application!

New training course: displaying and rendering graphics with Linux

Training – Displaying and rendering graphics with LinuxOver the past years, Bootlin engineers have accumulated significant knowledge and experience on the topic of display and graphics support in the Linux kernel, and based on this knowledge and experience, we have created a new training course: Displaying and rendering graphics with Linux.

This course targets engineers who need a detailed level of understanding of graphics concepts, graphics hardware and how the graphics stack is organized with Linux. The main topics covered are:

  • Image and color representation
  • Basic drawing
  • Basic and advanced operations
  • Hardware aspects overview
  • Hardware for display
  • Hardware for rendering
  • Memory aspects
  • Performance aspects
  • Software aspects overview
  • Kernel components in Linux
  • Userspace components with Linux

See the detailed agenda for all the details.

This course has a duration of two days, and is composed of lectures and demonstrations made by the trainer. It does not include practical labs, unlike all our other training courses. The development of the training materials is in progress, and they will be released under the same Creative Commons license that we use for all our training materials, once the first course has taken place (September 2019).

We can deliver this course on-site, anywhere in the world, please contact us for more details.

Feedback from the SiFive Tech Symposium in Grenoble

SiFive LogoSiFive is a semi-conductor company that produces chips based on the RISC-V architecture. On May 15th, they organized a Technical Symposium in Grenoble on May 15th and we took the opportunity to attend, as the agenda looked interesting.

It was especially nice having Krste Asanovic present many of the topics, wearing different hats (RISC-V Foundation Chairman of the Board and SiFive Co-Founder and Chief Architect). The RISC-V architecture and its history and use cases were presented. One of the main benefit of having a brand new ISA (instruction set architecture), Asanovic said, is that it doesn’t have to handle legacy instructions and compatibility. Moreover, RISC-V is a frozen ISA, the base instructions are frozen and optional extensions which have been approved are also frozen. Finally, the ISA is open and anybody can implement a CPU core. During the presentation, the RISC-V ISA was (obviously) favorably compared to competing ISAs, mainly ARM.

Another interesting topic was the presentation of SiFive’s business model. They want anyone, including small companies to be able to design an SoC fitting their particular product, instead of having to choose from a set of more general purpose SoC. This can be done by using an existing SiFive RISC-V core or by customizing one. SiFive then offers a library of IPs that can be added on the SoC and third party IPs are available through their Designshare program. They handle NDA, contract and licensing and will collect non recurring engineering costs and royalties once the SoC is mass produced but not during the prototyping phase. They first provide virtualized chips and then sample chips. For the core, they also provide RTL that can run on FPGAs. For mass production, SiFive partnered with TSMC and their customers can benefit from their process (down to 7nm).

The most relevant topic for us was the software ecosystem. There is a very nice will to get code upstream and this is the case for GCC, binutils, newlib, gdb, glibc, qemu. Clang/LLVM is coming up. Regarding the Linux kernel port, it still requires some work as the core architecture support is there but no devices drivers or device tree support yet. There is however a fully working vendor tree. FreeBSD seems to be in the same state.

Most of the remaining time was focused on the design and customization tool available here.
SiFive Chip Designer

SiFive also Sponsored Linus Sebastian (from Linus Tech Tips) for a video:

To conclude, it was an very interesting day. At Bootlin, we are delighted to see architecture designers and silicon vendors actively pushing software support upstream and we are looking forward to work on RISC-V platforms.

Building a Linux system for the STM32MP1: connecting an I2C sensor

After showing how to build and run a minimal Linux system for the STM32MP157 Discovery board in a previous blog post, we are now going to see how to connect an I2C sensor, adjust the Device Tree to enable the I2C bus and I2C device, and how to adjust the kernel configuration to enable the appropriate kernel driver.

List of articles in this series:

  1. Building a Linux system for the STM32MP1: basic system
  2. Building a Linux system for the STM32MP1: connecting an I2C sensor
  3. Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications
  4. Building a Linux system for the STM32MP1: setting up a Qt5 application development environment
  5. Building a Linux system for the STM32MP1: developing a Qt5 graphical application
  6. Building a Linux system for the STM32MP1: implementing factory flashing
  7. Building a Linux system for the STM32MP1: remote firmware updates

Choosing an I2C sensor

BME280 breakout boardFor this project, we wanted an I2C sensor that was at least capable of measuring the temperature, so we simply started by searching i2c temperature sensor on Amazon. After a bit of research, we found that the BME280 sensor from Bosch was available on several inexpensive break-out boards, and it already had a device driver in the upstream Linux kernel. When choosing hardware, it is always important to check whether it is already supported or not in the upstream Linux kernel. Having a driver already integrated in the upstream Linux kernel has a number of advantages:

  • The driver is readily available, you don’t have to integrate a vendor-provided driver, with all the possible integration issues
  • The driver has been reviewed by the Linux kernel maintainers, so you can be pretty confident of the code quality
  • The driver is using standard Linux interfaces, and not some vendor-specific one
  • The driver will be maintained in the long run by the kernel community, so you can continue to update your Linux kernel to benefit from security updates, bug fixes and new features

In addition, it also turns out that the BME280 sensor not only provides temperature sensing, but also pressure and humidity, which makes it even more interesting.

Among the numerous inexpensive BME280 break-out boards, we have chosen specifically this one, but plenty of others are available. The following details will work with any other BME280-based break-out board.

Connecting the I2C sensor

From a connectivity point of view, our I2C sensor is pretty simple: a VIN signal for power, a GND signal for ground, a SCL for the I2C clock and a SDA for the I2C data.

To understand how to connect this sensor to the Discovery board, we need to start with the board user manual.

The Discovery board has two main expansion connectors: CN2 and the Arduino connectors.

Connector CN2

Connector CN2 is a 40-pin male header on the front side of the board:

CN2 connector

Section 7.17 of the board user manual documents the pin-out of this connector. There is one I2C bus available, through the I2C1_SDA (pin 27) and I2C1_SCL (pin 28) signals.

CN2 I2C1

Arduino connectors

Connectors CN13, CN14, CN16, CN17 are female connectors on the back side of the board. They are compatible in pin-out and form-factor with the Arduino connector:

Arduino connectors

Section 7.16 of the board user manual documents the pin-out for these connectors. There is one I2C bus available as well in CN13, through the I2C5_SDA (pin 9) and I2C5_SCL (pin 10) signals.

CN13

Choosing the connector

According to the block diagram in Figure 3 of the board user manual, the I2C1 bus is already used to connect the touchscreen, the USB hub, the audio codec and the HDMI transceiver. However, I2C5 doesn’t seem to be used at all. In addition, with the screen mounted on the Discovery board, the CN2 connector is beneath the screen, which makes it a bit more difficult to use than the Arduino connectors on the back side.

We will therefore use the I2C5 bus, through the Arduino connector CN13. Pin 9 will be used to connect the data signal of our sensor, and pin 10 will be used to connect the clock signal of our sensor.

Finalizing the connectivity

We still have to find out how to connect the VIN and GND pins. According to the BME280 datasheet, VDDmain supply voltage range: 1.71V to 3.6V. The Arduino connector CN16 provides either 3.3V or 5V, so we’ll chose 3.3V (pin 4). And this connector also has multiple ground pins, among which we will chose pin 6.

Overall, this gives us the following connections:

Sensor signal Arduino connector Pin
VIN CN16 pin 4
GND CN16 pin 6
SDA CN13 pin 9
SCL CN13 pin 10

Here are a few pictures of the setup. First, on the sensor side, we have a purple wire for VIN, a grey wire for GND, a white wire for SCL and a black wire for SDA:

I2C sensor connection

On the board side, we can see the purple wire (VIN) going to pin 4 of CN16, the grey wire (GND) going to pin 6 of CN16, the white wire (SCL) going to pin 10 of CN13 and the black wire (SDA) going to pin 9 of CN13.

I2C sensor connected to the board

With this we’re now all set in terms of hardware setup, let’s move on to enabling the I2C bus in Linux!

Enabling the I2C bus

An introduction to the Device Tree

In order to enable the I2C bus, we’ll need to modify the Device Tree, so we’ll first need to give a few details about what Device Tree is. If you read again our previous blog post in this series, we already mentioned the Device Tree. As part of the Buildroot build process, a file called stm32mp157c-dk2.dtb is produced, and this file is used at boot time by the Linux kernel: it is the Device Tree.

On most embedded architectures, devices are connected using buses that do not provide any dynamic enumeration capabilities. While buses like USB or PCI provide such capabilities, popular buses used on embedded architectures like memory-mapped buses, I2C, SPI and several others do not allow the operating system to ask the hardware: what peripherals are connected ? what are their characteristics ?. The operating system needs to know which devices are available and what their characteristics are. This is where the Device Tree comes into play: it is a data structure that describes in the form of a tree all the devices that we have in our hardware platform, so that the Linux kernel knows the topology of the hardware.

On ARM platforms, each particular board is described by its own Device Tree file. In our case, the STM32MP157 Discovery Kit 2 is described by the Device Tree file arch/arm/boot/dts/stm32mp157c-dk2.dts in the Linux kernel source code. This human-readable source file, with a .dts extension, is compiled during the Linux kernel build process into a machine-readable binary file, with a .dtb extension.

This stm32mp157c-dk2.dts describes the hardware of our Discovery Kit 2 platform. In fact, it only describes what is specific to the Discovery Kit 2: the display panel, the touchscreen, the WiFi and Bluetooth chip. Everything else is common with the Discovery Kit 1 platform, which is why the stm32mp157c-dk2.dts file includes the arm/boot/dts/stm32mp157a-dk1.dts file. Indeed, stm32mp157a-dk1.dts describes the hardware on the Discovery Kit 1, which is the same as the Discovery Kit 2, without the display, touchscreen and WiFi/Bluetooth chip.

In turn, the stm32mp157a-dk1.dts includes three other Device Tree files:

At this point, we won’t give much more generic details about the Device Tree, as it’s an entire topic on its own. For additional details, you could check the Device Tree for Dummies presentation from your author (slides, video) or the devicetree.org web site.

I2C controllers in the Device Tree

Zooming in to the topic of I2C, we can see that arm/boot/dts/stm32mp157c.dtsi describes 6 I2C controllers through six different nodes in the Device Tree:

  • i2c1: i2c@40012000
  • i2c2: i2c@40013000
  • i2c3: i2c@40014000
  • i2c4: i2c@5c002000
  • i2c5: i2c@40015000
  • i2c6: i2c@5c009000

This list of six I2C controllers nice matches the list of I2C controllers in the STM32MP157 datasheet, and their base address in the memory map, section 2.5.2:

I2C1I2C2I2C3I2C4I2C5I2C6

In the file arm/boot/dts/stm32mp157a-dk1.dts, we can see that the I2C1 bus is enabled, and that a cs42l51 audio codec (I2C address 0x4a) and a sii9022 HDMI transceiver (I2C address 0x39) are connected to it:

&i2c1 {
	status = "okay";

	cs42l51: cs42l51@4a {
		compatible = "cirrus,cs42l51";
		reg = <0x4a>;
	};

	hdmi-transmitter@39 {
		compatible = "sil,sii9022";
		reg = <0x39>;
	};
};

Also, on the I2C4 bus, we can see the USB-C controller (I2C address 0x28) and the PMIC (I2C address 0x33):

&i2c4 {
	status = "okay";

	typec: stusb1600@28 {
		compatible = "st,stusb1600";
		reg = <0x28>;
	};

	pmic: stpmic@33 {
		compatible = "st,stpmic1";
		reg = <0x33>;
	};
};

So, to enable our I2C5 bus, we will simply need to add:

&i2c5 {
	status = "okay";
	clock-frequency = <100000>;
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_pins_sleep_a>;
};

to enable the bus. This piece of code adds the following Device Tree properties to the I2C5 Device Tree node:

  • status = "okay" which simply tells the Linux kernel: I really intend to use this device, so please enable whatever driver is needed to use this device
  • clock-frequency = <100000> tells Linux at which frequency we want to operate the I2C bus: in this case, 100 kHz
  • The pinctrl properties configure the pin muxing, so that the pins are configured in the I2C function when the system is running (the default state) and into a different state to preserve power when the system is in suspend to RAM (sleep state). Both i2c5_pins_a and i2c5_pins_sleep_a are already defined in arch/arm/boot/dts/stm32mp157-pinctrl.dtsi.

For now, this doesn’t describe any device on the bus, but should be sufficient to have the bus enabled in Linux. The question now is how to make this modification in our Device Tree in the proper way ?

Changing the Linux kernel source code

When Buildroot builds each package, it extracts its source code in output/build/<package>-<version>, so the source code of our Linux kernel has been extracted in output/build/linux-custom/. One could therefore be tempted to make his code changes directory in output/build/linux-custom/, but this has a number of major drawbacks:

  1. output/build/linux-custom/ is not under version control: it is not part of a Linux kernel Git repository, so you can’t version control your changes, which is really not great
  2. output/build/linux-custom/ is a temporary folder: if you do a make clean in Buildroot, this folder will be entirely removed, and re-created during the next Buildroot build

So, while doing a change directly in output/build/linux-custom/ is perfectly fine for quick/temporary changes, it’s not a good option to make changes that will be permanent.

To do this in a proper way, we will use a feature of Buildroot called pkg_OVERRIDE_SRCDIR, which is documented in section 8.12.6 Using Buildroot during development of the Buildroot manual. This feature allows to tell Buildroot: for a given package, please don’t download it from the usual location, but instead take the source code from a specific location on my system. This specific location will of course be under version control, and located outside of Buildroot, which allows to solve the two issues mentioned above.

So, let’s get set this up for the Linux kernel source code:

  1. Start in the parent folder of Buildroot, so that the Linux kernel source code ends up being side-by-side with Buildroot
  2. Clone the official upstream Linux kernel repository. Even though we could directly clone the STMicro Linux kernel repository, your author always finds it nicer to have the origin Git remote set up to the official upstream Git repository.
    git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
    
  3. Move inside this Git repository
    cd linux/
    
  4. Add the STMicro Linux kernel repository as a remote:
    git remote add stmicro https://github.com/STMicroelectronics/linux.git
    
  5. Fetch all the changes from the STMicro Linux kernel repository:
    git fetch stmicro
    
  6. Create a new branch, called bme280, based on the tag v4.19-stm32mp-r1.2. This tag is the one used by our Buildroot configuration as the version of the Linux kernel. The following command also moves to this new branch as the same time:
    git checkout -b bme280 v4.19-stm32mp-r1.2
    
  7. At this point, our linux/ folder contains the exact same source code as what Buildroot has retrieved. It is time to make our Device Tree change by editing arch/arm/boot/dts/stm32mp157c-dk2.dts and at the end of it, add:

    &i2c5 {
    	status = "okay";
    	clock-frequency = <100000>;
    	pinctrl-names = "default", "sleep";
    	pinctrl-0 = <&i2c5_pins_a>;
    	pinctrl-1 = <&i2c5_pins_sleep_a>;
    };
    

    Once done, we need to tell Buildroot to use our kernel source code, using the pkg_OVERRIDE_SRCDIR mechanism. To this, create a file called local.mk, in the top-level Buildroot source directory, which contains:

    LINUX_OVERRIDE_SRCDIR = $(TOPDIR)/../linux
    

    This tells Buildroot to pick the Linux kernel source from $(TOPDIR)/../linux. We’ll now ask Buildroot to wipe out its Linux kernel build, and do a build again:

    $ make linux-dirclean
    $ make
    

    If you look closely at what Buildroot will do, it will do a rsync of the Linux kernel source code from your linux/ Git repository to output/build/linux-custom in Buildroot, and then do the build. You can check output/build/linux-custom/arch/arm/boot/dts/stm32mp157c-dk2.dts to make sure that your I2C5 change is there!

    If that is the case, then reflash output/images/sdcard.img on your SD card, and run the new system on the board. It’s now time to test the I2C bus!

    Testing the I2C bus

    After booting the new system on your Discovery board and logging in as root, let’s have a look at all I2C related devices:

    # ls -l /sys/bus/i2c/devices/
    total 0
    lrwxrwxrwx    0-002a -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-002a
    lrwxrwxrwx    0-0038 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-0038
    lrwxrwxrwx    0-0039 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-0039
    lrwxrwxrwx    0-004a -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-004a
    lrwxrwxrwx    2-0028 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2/2-0028
    lrwxrwxrwx    2-0033 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2/2-0033
    lrwxrwxrwx    i2c-0 -> ../../../devices/platform/soc/40012000.i2c/i2c-0
    lrwxrwxrwx    i2c-1 -> ../../../devices/platform/soc/40015000.i2c/i2c-1
    lrwxrwxrwx    i2c-2 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2
    lrwxrwxrwx    i2c-3 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/i2c-3
    

    This folder is part of the sysfs filesystem, which is used by the Linux kernel to expose to user-space applications all sort of details about the hardware devices connected to the system. More specifically, in this folder, we have symbolic links for two types of devices:

    • The I2C busses: i2c-0, i2c-1, i2c-2 and i2c-3. It is worth mentioning that the bus numbers do not match the datasheet: they are simply numbered from 0 to N. However, the i2c-0 symbolic link shows it’s the I2C controller at base address 0x40012000, so it’s I2C1 in the datasheet, i2c-1 is at base address 0x40015000 so it’s I2C5 in the datasheet, and i2c-2 at base address 0x5c002000 is I2C4 in the datasheet. i2c-3 is special as it’s not an I2C bus provided by the SoC itself, but the I2C bus provided by the HDMI transmitter to talk with the remote HDMI device (since this is unrelated to our discussion, we won’t go into more details on this).
    • The I2C devices: 0-002a, 0-0038, 0-0039, 0-004a, 2-0028, 2-033. These entries have the form B-SSSS where B is the bus number and SSSS is the I2C address of the device. So you can see that for example 0-004a corresponds to the cs42l51 audio codec we mentioned earlier.

    In our case, we are interested by I2C5, which is known by Linux as i2c-1. We will use the i2cdetect utility, provided by Busybox, to probe the different devices on this bus:

    # i2cdetect -y 1
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- 76 --                         
    

    Interesting, we have a device at address 0x76! Try to disconnect VIN of your I2C sensor, and repeat the command:

    # i2cdetect -y 1
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- --                         
    

    The device at 0x76 has disappeared, so it looks like our sensor is at I2C address 0x76. To confirm this, let’s have a look at what the BME280 datasheet says about the I2C address of the device, in section 6.2 I2C Interface:

    BME280 I2C address

    So, the I2C address is indeed 0x76 when the SDO pin of the sensor is connected to GND, which is probably what our BME280 break-out board is doing. It matches the address we have detected with i2cdetect!

    Now, let’s talk to our device. According to section 5.4 Register description of the datasheet, there is a Chip ID register, at offset 0xD0 that is supposed to contain 0x60:

    BME280 Chip ID

    We can read this register using the i2cget command:

    # i2cget -y 1 0x76 0xd0
    0x60
    

    Good, this matches the expected value according to the BME280 datasheet, so it seems like communication with our I2C device is working, let’s move on to enabling the BME280 sensor driver.

    Enabling the sensor driver

    As discussed earlier, this BME280 sensor already has a driver in the upstream Linux kernel, in the IIO subsystem. IIO stands for Industrial Input/Output, and this subsystems contains a lot of drivers for various ADCs, sensors and other types of measurement/acquisition devices. In order to use this driver for our BME280 device, we will essentially have to do two things:

    1. Enable the driver in our Linux kernel configuration, so that the driver code gets built as part of our kernel image
    2. Describe the BME280 device in our Device Tree so that the Linux kernel knows we have one such device, and how it is connected to the system

    Adjusting the kernel configuration

    In the previous blog post, we explained that the Linux kernel configuration used to build the kernel for the STM32 Discovery board was located at board/stmicroelectronics/stm32mp157-dk/linux.config. Obviously, we are not going to edit this file manually: we need to run the standard Linux kernel configuration tools.

    It turns out that Buildroot has convenient shortcuts to manipulate the Linux kernel configuration. We can run the Linux kernel menuconfig configuration tool by running:

    $ make linux-menuconfig
    

    At this point, it is really important to not be confused by the fact that both Buildroot and the Linux kernel use the same configuration utility, but each have its own configuration. The Buildroot configuration describes your overall system (target architecture, which software components you want, which type of filesystem you want, etc.) while the Linux kernel configuration describes the kernel configuration itself (which drivers you want, which kernel features you need, etc.). So make sure to not confuse the menuconfig of Buildroot with the menuconfig of the Linux kernel!

    Once you have run make linux-menuconfig, the menuconfig of the Linux kernel will show up. You will then enable the following option:

    Device Drivers
    +- Industrial I/O support
       +- Pressure sensors
          +- Bosch Sensortec BMP180/BMP280 pressure sensor I2C driver
    

    Make sure to enable this option with a star <*> so that the driver is compiled inside the kernel image itself and not as a separate kernel module. You can then exit the menuconfig utility, and confirm that you want to save the configuration.

    At this point, the Linux kernel configuration file in output/build/linux-custom/.config has been changed. You can confirm it by running:

    $ grep CONFIG_BMP280 output/build/linux-custom/.config
    CONFIG_BMP280=y
    CONFIG_BMP280_I2C=y
    CONFIG_BMP280_SPI=y
    

    However, as we explained earlier, the output/build/linux-custom/ folder is temporary: it would be removed when doing a Buildroot make clean. We would like to permanently keep our Linux kernel configuration. Once again, Buildroot provides a nice shortcut to do this:

    $ make linux-update-defconfig
    

    After running this command, the kernel configuration file board/stmicroelectronics/stm32mp157-dk/linux.config has been updated, and this file is not temporary, and is under version control. If you run git diff, you can see the change on this file:

    $ git diff
    [...]
    index 878a0c39f1..12f3e22647 100644
    --- a/board/stmicroelectronics/stm32mp157-dk/linux.config
    +++ b/board/stmicroelectronics/stm32mp157-dk/linux.config
    @@ -169,6 +169,7 @@ CONFIG_STM32_LPTIMER_CNT=y
     CONFIG_STM32_DAC=y
     CONFIG_IIO_HRTIMER_TRIGGER=y
     CONFIG_IIO_STM32_LPTIMER_TRIGGER=y
    +CONFIG_BMP280=y
     CONFIG_PWM=y
     CONFIG_PWM_STM32=y
     CONFIG_PWM_STM32_LP=y
    

    We’re all set for the kernel configuration!

    Describing the BME280 in the Device Tree

    We now need to tell the Linux kernel that we have a BME280 sensor and how it is connected to the system, which is done by adding more details into our Device Tree. We have already enabled the I2C5 bus, and we now need to describe one device connected to it: this gets done by creating a child node of the I2C controller node.

    How do we know what to write in the Device Tree node describing the BME280 ? Using Device Tree bindings. Those bindings are specification documents that describe how a given device should be represented in the Device Tree: which properties are available, what are their possible values, etc. All Device Tree bindings supported by the Linux kernel are documented in Documentation/devicetree/bindings in the Linux kernel source code. For our BME280 device, the binding is at Documentation/devicetree/bindings/iio/pressure/bmp085.yaml.

    This document tells us that we have one required property, the compatible property, with the range of possible values. Since we have a BME280 sensor, we’ll use bosch,bme280. The other properties are optional, so we’ll ignore them for now. This binding also documents a reg property, which is used to provide to the Linux kernel the I2C address of the device.

    So, we’ll go back to our linux/ directory outside of Buildroot, where we cloned the Linux kernel repository, and we’ll adjust our Device Tree file arch/arm/boot/dts/stm32mp157c-dk2.dts so that it contains:

    &i2c5 {
    	status = "okay";
    	clock-frequency = <100000>;
    	pinctrl-names = "default", "sleep";
    	pinctrl-0 = <&i2c5_pins_a>;
    	pinctrl-1 = <&i2c5_pins_sleep_a>;
    
    	pressure@76 {
    		compatible = "bosch,bme280";
    		reg = <0x76>;
    	};
    };
    

    Re-building the kernel

    Let’s now ask Buildroot to rebuild the Linux kernel, with our Device Tree change and kernel configuration change. Instead of rebuilding from scratch, we’ll just ask Buildroot to restart the build of the Linux kernel, which will be much faster:

    $ make linux-rebuild
    

    As part of this, Buildroot will re-run rsync from our linux/ kernel Git repository to output/build/linux-custom/, so that we really build the latest version of our code, which includes our Device Tree change.

    However, this just rebuilds the Linux kernel, and not the complete SD card image, so also run:

    $ make
    

    To regenerate the SD card image, write it on your SD card, and boot your system.

    Testing the sensor

    After booting the system, if we check /sys/bus/i2c/devices, a new entry has appeared:

    lrwxrwxrwx    1-0076 -> ../../../devices/platform/soc/40015000.i2c/i2c-1/1-0076
    

    If we following this symbolic link, we can see a number of interesting information:

    # ls -l /sys/bus/i2c/devices/1-0076/
    total 0
    lrwxrwxrwx    driver -> ../../../../../../bus/i2c/drivers/bmp280
    drwxr-xr-x    iio:device2
    -r--r--r--    modalias
    -r--r--r--    name
    lrwxrwxrwx    of_node -> ../../../../../../firmware/devicetree/base/soc/i2c@40015000/pressure@76
    drwxr-xr-x    power
    lrwxrwxrwx    subsystem -> ../../../../../../bus/i2c
    -rw-r--r--    uevent
    

    Here we can see that this device is bound with the device driver named bmp280, and that its Device Tree node is base/soc/i2c@40015000/pressure@76.

    Now, to actually use the sensor, we need to understand what is the user-space interface provided by IIO devices. The kernel documentation gives some hints:

    There are two ways for a user space application to interact with an IIO driver.

    • /sys/bus/iio/iio:deviceX/, this represents a hardware sensor and groups together the data channels of the same chip.
    • /dev/iio:deviceX, character device node interface used for buffered data transfer and for events information retrieval.

    So, we’ll try to explore the /sys/bus/iio/ option:

    # ls -l /sys/bus/iio/devices/
    total 0
    lrwxrwxrwx    iio:device0 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:adc@0/iio:device0
    lrwxrwxrwx    iio:device1 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:adc@100/iio:device1
    lrwxrwxrwx    iio:device2 -> ../../../devices/platform/soc/40015000.i2c/i2c-1/1-0076/iio:device2
    lrwxrwxrwx    iio:device3 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:temp/iio:device3
    lrwxrwxrwx    trigger0 -> ../../../devices/platform/soc/40004000.timer/trigger0
    

    Here we can see a number of IIO devices: our IIO device is iio:device2, as can be seen by looking at the target of the symbolic links. The other ones are IIO devices related to the ADC on the STM32 processor. Let’s check what we have inside /sys/bus/iio/devices/iio:device2/:

    # ls -l /sys/bus/iio/devices/iio\:device2/
    total 0
    -r--r--r--    dev
    -rw-r--r--    in_humidityrelative_input
    -rw-r--r--    in_humidityrelative_oversampling_ratio
    -rw-r--r--    in_pressure_input
    -rw-r--r--    in_pressure_oversampling_ratio
    -r--r--r--    in_pressure_oversampling_ratio_available
    -rw-r--r--    in_temp_input
    -rw-r--r--    in_temp_oversampling_ratio
    -r--r--r--    in_temp_oversampling_ratio_available
    -r--r--r--    name
    lrwxrwxrwx    of_node -> ../../../../../../../firmware/devicetree/base/soc/i2c@40015000/pressure@76
    drwxr-xr-x    power
    lrwxrwxrwx    subsystem -> ../../../../../../../bus/iio
    -rw-r--r--    uevent
    

    This is becoming interesting! We have a number of files that we can read to get the humidity, pressure, and temperature:

    # cat /sys/bus/iio/devices/iio\:device2/in_humidityrelative_input 
    49147
    # cat /sys/bus/iio/devices/iio\:device2/in_pressure_input 
    101.567167968
    # cat /sys/bus/iio/devices/iio\:device2/in_temp_input 
    24380
    

    Now, let’s check the kernel documentation at Documentation/ABI/testing/sysfs-bus-iio to understand the units used in these files:

    What:		/sys/bus/iio/devices/iio:deviceX/in_tempX_input
    Description:
    		Scaled temperature measurement in milli degrees Celsius.
    
    What:		/sys/bus/iio/devices/iio:deviceX/in_pressure_input
    Description:
    		Scaled pressure measurement from channel Y, in kilopascal.
    
    What:		/sys/bus/iio/devices/iio:deviceX/in_humidityrelative_input
    Description:
    		Scaled humidity measurement in milli percent.
    

    So here we are: we are able to read the data from our sensor, and the Linux kernel driver does all the conversion work to convert the raw values from the sensors into usable values in meaningful units.

    Turning our kernel change into a patch

    Our Device Tree change is for now only located in our local Linux kernel Git repository: if another person builds our Buildroot configuration, he won’t have access to this Linux kernel Git repository, which Buildroot knows about thanks to the LINUX_OVERRIDE_SRCDIR variable. So what we’ll do now is to generate a Linux kernel patch that contains our Device Tree change, add it to Buildroot, and ask Buildroot to apply it when building the Linux kernel. Let’s get started.

    First, go in your Linux kernel Git repository in linux/, review your Device Tree change with git diff, and if everything is alright, make a commit out of it:

    $ git commit -as -m "ARM: dts: add support for BME280 sensor on STM32MP157 DK2"
    

    Then, generate a patch out of this commit:

    $ git format-patch HEAD^
    

    This will create a file called 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch that contains our Device Tree change.

    Now, back in Buildroot in the buildroot/ folder, create the board/stmicroelectronics/stm32mp157-dk/patches/ folder and a sub-directory board/stmicroelectronics/stm32mp157-dk/patches/linux. Copy the patch into this folder, so that the file hierarchy looks like this:

    $ tree board/stmicroelectronics/stm32mp157-dk/
    board/stmicroelectronics/stm32mp157-dk/
    ├── genimage.cfg
    ├── linux.config
    ├── overlay
    │   └── boot
    │       └── extlinux
    │           └── extlinux.conf
    ├── patches
    │   └── linux
    │       └── 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch
    ├── readme.txt
    └── uboot-fragment.config
    

    Now, run Buildroot’s menuconfig:

    $ make menuconfig
    

    And in Build options, set global patch directories to the value board/stmicroelectronics/stm32mp157-dk/patches/. This tells Buildroot to apply patches located in this folder whenever building packages. This way, when the linux package will be built, our patch in board/stmicroelectronics/stm32mp157-dk/patches/linux/ will be applied.

    We can now remove the local.mk file to disable the pkg_OVERRIDE_SRCDIR mechanism, and ask Buildroot to rebuild the Linux kernel:

    $ rm local.mk
    $ make linux-dirclean
    $ make
    

    If you pay attention to the Linux kernel build process, you will see that during the Patching step, our Device Tree patch gets applied:

    >>> linux custom Patching
    
    Applying 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch using patch: 
    patching file arch/arm/boot/dts/stm32mp157c-dk2.dts
    

    You can of course reflash the SD card at the end of the build, and verify that everything still works as expected.

    Let’s save our Buildroot configuration change:

    $ make savedefconfig
    

    And commit our Buildroot changes:

    $ git add board/stmicroelectronics/stm32mp157-dk/linux.config
    $ git add board/stmicroelectronics/stm32mp157-dk/patches/
    $ git add configs/stm32mp157_dk_defconfig
    $ git commit -s -m "configs/stm32mp157_dk: enable support for BME280 sensor"
    

    We can now share our Buildroot change with others: they can build our improved system which has support for the BME280 sensor.

    Conclusion

    You can find the exact Buildroot source code used to reproduce the system used in this article in the branch at 2019.02/stm32mp157-dk-blog-2.

    In this article, we have learned a lot of things:

    • How to connect an I2C sensor to the Discovery board
    • What is the Device Tree, and how it is used to describe devices
    • How to use Buildroot’s pkg_OVERRIDE_SRCDIR mechanism
    • How to enable the I2C bus in the Device Tree and test its operation using i2cdetect and i2cget
    • How to change the Linux kernel configuration to enable a new driver
    • How to interact using sysfs with a sensor supported by the IIO subsystem
    • How to generate a Linux kernel patch, and add it into Buildroot

    In our next article, we’ll look at adding support for the Qt5 graphical library into our system, as a preparation to developing a Qt5 application that will display our sensor measurements on the Discovery board screen.

Building a Linux system for the STM32MP1: basic system

As we announced recently, we are going to publish a series of blost post that describes how to build an embedded Linux device based on the STM32MP1 platform, using the Buildroot build system. In this first article, we are going to see how to create a basic Linux system, with minimal functionality. The hardware platform used in these articles is the STM32MP157-DK2.

List of articles in this series:

  1. Building a Linux system for the STM32MP1: basic system
  2. Building a Linux system for the STM32MP1: connecting an I2C sensor
  3. Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications
  4. Building a Linux system for the STM32MP1: setting up a Qt5 application development environment
  5. Building a Linux system for the STM32MP1: developing a Qt5 graphical application
  6. Building a Linux system for the STM32MP1: implementing factory flashing
  7. Building a Linux system for the STM32MP1: remote firmware updates

What is Buildroot?

A Linux system is composed of a potentially large number of software components coming from different sources:

  • A bootloader, typically U-Boot, responsible for doing some minimal HW initialization, loading the Linux kernel and starting it
  • The Linux kernel itself, which implements features such as process management, memory management, scheduler, filesystems, networking stack and of course all device drivers for your hardware platform
  • User-space libraries and applications coming from the open-source community: command line tools, graphical libraries, networking libraries, cryptographic libraries, and more.
  • User-space libraries and applications developed internally, implementing the “business logic” of the embedded system

In order to assemble a Linux system with all those software components, one typically has two main choices:

  • Use a binary distribution, like Debian, Ubuntu or Fedora. Several of these distributions have support for the ARMv7 architecture. The main advantage of this solution is that it is easy: these binary distributions are familiar to most Linux users, they have a nice and easy-to-use package management system, all packages are pre-compiled so it is really fast to generate a Linux system. However, Linux systems generated this way are typically difficult to customize (software components are pre-built, so you cannot easily tweak their configuration to your needs) and difficult to optimize (in terms of footprint or boot time).
  • Use a build system, like Buildroot or Yocto/OpenEmbedded. These build systems build an entire Linux system from source code, which means that it can be highly customized and optimized to your needs. Of course, it is less simple than using a binary distribution and because you are building all components from source code, a non-negligible amount of CPU time will be spent on compiling code.

BuildrootIn this series of blog post, we have chosen to use Buildroot, which is an easy-to-use build system, which is a good match for engineers getting started with embedded Linux. For more general details about Buildroot, you can read the freely available training materials of our Embedded Linux development with Buildroot training course.

Buildroot is a set of Makefiles and script that automates the process of download the source code of the different software components, extract them, configure them, build them and install them. It ultimately generates a system image that is ready to be flashed, and which typically contains the bootloader, the Linux kernel image and the root filesystem. It is important to understand that Buildroot itself does not contain the source code for Linux, U-Boot or any other component: it is only a set of scripts/recipes that describes where to download the source code from, and how to build it.

Principle of an embedded Linux build system

Building the minimal system with Buildroot

Let’s started by getting the source of Buildroot from its upstream Git repository:

git clone git://git.buildroot.net/buildroot
cd buildroot

Starting a Buildroot configuration is then typically done by running make menuconfig, and then selecting all the relevant options for your system. Here, we are instead going to use a pre-defined configuration that we created for the STM32MP157-DK2 platform. This pre-defined configuration has been submitted to the upstream Buildroot project, but has not yet been merged as of this writing, so we’ll use an alternate Git branch:

git remote add tpetazzoni https://github.com/tpetazzoni/buildroot.git
git fetch tpetazzoni
git checkout -b stm32mp157-dk2 tpetazzoni/2019.02/stm32mp157-dk

The 2019.02/stm32mp157-dk branch in your author’s Buildroot Git repository is based on upstream Buildroot 2019.02.x branch and contains 4 additional patches needed to support the STM32MP157-DK2 platform.

Let’s continue by telling Buildroot to load the pre-defined configuration for the STM32MP157-DK2:

make stm32mp157_dk_defconfig

We could start the build right away, as this configuration works fine, but to illustrate how to modify the configuration (and speed up the build!) we will adjust one aspect of the system configuration. To do so, let’s run Buildroot’s menuconfig. People who have already configured the Linux kernel should be familiar with the tool, as it is the exact same configuration utility.

make menuconfig

At this point, if the command fails due to the ncurses library being missing, make sure to install the libcnurses-dev or ncurses-devel package on your Linux distribution (the exact package name depends on the distribution you’re using).

Once in menuconfig, go to the Toolchain sub-menu. By default the Toolchain type is Buildroot toolchain. Change it to External toolchain by pressing the Enter key. When Buildroot toolchain is selected, Buildroot builds its own cross-compiler, which takes quite some time. Selecting External toolchain tells Buildroot to use a pre-existing cross-compiler, which in our case is the one provided by ARM for the ARMv7 architecture.

Exit menuconfig and save the configuration. It is now time to start the build by running make. However, your author generally likes to keep the output of the build in a log file, using the following incantation:

make 2>&1 | tee build.log

Now that Buildroot starts by checking if your system has a number of required packages installed, and will abort if not. Please follow section System requirements > Mandatory packages of the Buildroot manual to install all the appropriate dependencies. Restart the make command once all dependencies have been installed.

The build process took 10 minutes on your author’s machine. All the build output is conveniently grouped in the sub-directory named output/, in which the most important results are in output/images/:

  • output/images/zImage is the Linux kernel image
  • output/images/stm32mp157c-dk2.dtb is the Device Tree Blob, i.e the piece of data that describes to the Linux kernel the hardware it is running on. We’ll talk more about Device Tree in the second blog post of this series
  • output/images/rootfs.{ext4,ext2} is the image of the root filesystem, i.e the filesystem that contains all the user-space libraries and applications. It’s using the ext4 filesystem format, which is the de-facto standard filesystem format in Linux for block storage.
  • output/images/u-boot-spl.stm32 is the first stage bootloader
  • output/images/u-boot.img is the second stage bootloader
  • output/images/sdcard.img is a complete, ready-to-use SD card image, which was generated from the previous images

Flashing and testing the system

First things first, we’ll need to write sdcard.img to a microSD card:

sudo dd if=output/images/sdcard.img of=/dev/mmcblk0 bs=1M conv=fdatasync status=progress

Of course, make sure that, on your system, the microSD card is really identified as /dev/mmcblk0. And beware that all the data on your microSD card will be lost!

Insert the microSD card in the microSD card connector of the STM32MP157-DK2 board, i.e connector CN15.

Connect a USB to micro-USB cable between your PC and the connector labeled ST-LINK CN11 on the board. A device called /dev/ttyACM0 will appear on your PC, through which you’ll be able to access the board’s serial port. Install and run a serial port communication program on your PC, your author’s favorite is the very minimalistic picocom:

picocom -b 115200 /dev/ttyACM0

Finally, power up the board by connecting a USB-C cable to connector PWR_IN CN6. You should then see a number of messages on the serial port, all the way up to Buildroot login:. You can then login with the root user, no password will be requested.

STM32MP157-DK2 in situation

How is the system booting ?

Let’s look at the main steps of the boot process, by studying the boot log visible on the serial port:

U-Boot SPL 2018.11-stm32mp-r2.1 (Apr 24 2019 - 10:37:17 +0200)

This message is printed by the first stage bootloader, i.e the code contained in the file u-boot-spl.stm32, compiled as part of the U-Boot bootloader. This first stage bootloader is directly loaded by the STM32MP157 system-on-chip. This first stage bootloader must be small enough to fit inside the STM32MP157 internal memory.

U-Boot 2018.11-stm32mp-r2.1 (Apr 24 2019 - 10:37:17 +0200)

This message is printed by the second stage bootloader, which was loaded from storage into external memory by the first stage bootloader. This second stage bootloader is the file u-boot.img, which was also compiled as part of the U-Boot bootloader.

Retrieving file: /boot/zImage
Retrieving file: /boot/stm32mp157c-dk2.dtb

These messages are printed by the second stage bootloader: we see it is loading the Linux kernel image (file zImage) and the Device Tree Blob describing our hardware platform (file stm32mp157c-dk2.dtb). It indicates that U-Boot has loaded both files into memory: it is now ready to start the Linux kernel.

Starting kernel ...

This is the last message printed by U-Boot before jumping into the kernel.

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 4.19.26 (thomas@windsurf) (gcc version 8.2.1 20180802 (GNU Toolchain for the A-profile Architecture 8.2-2018.11 (arm-rel-8.26))) #1 SMP PREEMPT Wed Apr 24 10:38:00 CEST 2019

And immediately after that, we have the first messages of the Linux kernel, showing the version of Linux and the date/time it was built. Numerous other kernel messages are then displayed, until:

[    3.248315] VFS: Mounted root (ext4 filesystem) readonly on device 179:4.

This message indicates that the kernel has mounted the root filesystem. After this point, the kernel will start the first user-space process, so the next messages are user-space services being initialized:

Starting syslogd: OK
[...]
Welcome to Buildroot
buildroot login: 

Until we reach a login prompt.

Exploring the system

After logging in as root, you have access to a regular Linux shell, with most basic Linux commands available. You can run ps to see the processes, run ls / to see the contents of the root filesystem, etc.

You can also play a bit with the hardware, for example to turn on and off one of the LEDs of the board:

echo 255 > /sys/class/leds/heartbeat/brightness
echo 0 > /sys/class/leds/heartbeat/brightness

Understanding the Buildroot configuration

So far, we have used a pre-defined Buildroot configuration, without really understanding what it does and how it built this basic system for our board. So let’s go back in make menuconfig and see how Buildroot was configured.

In the Target options menu, obviously the ARM Little Endian architecture was chosen, and more specifically Cortex-A7 was chosen as the Target Architecture Variant. Indeed the entire Linux system runs on the Cortex-A7 cores.

In the Build options menu, nothing was changed from the default values.

In the Toolchain menu, we previously modified to use an External toolchain to use a pre-existing cross-compiler and save on build time. All other options were kept as their default.

In the System configuration menu, we defined the following things:

  • Root filesystem overlay directories is set to board/stmicroelectronics/stm32mp157-dk/overlay/. This option tells Buildroot that the contents of the board/stmicroelectronics/stm32mp157-dk/overlay/ directory must be copied into the root filesystem at the end of the build. It allows to add custom files to the root filesystem.
  • Custom scripts to run after creating filesystem images is set to support/scripts/genimage.sh and the related option Extra arguments passed to custom scripts is set to -c board/stmicroelectronics/stm32mp157-dk/genimage.cfg. This tells Buildroot to call this genimage.sh script at the very end of the build: its purpose is to generate the final SD card image we have used.

In the Kernel menu, we have obviously configured which Linux kernel version and configuration should be used:

  • We are downloading the Linux kernel source code as a tarball from Github, using a custom Buildroot macro called github. Based on this information, Buildroot will go to the Git repository at https://github.com/STMicroelectronics/linux/, and get the kernel version identified by the tag v4.19-stm32mp-r1.2
  • Configuration file path is set to board/stmicroelectronics/stm32mp157-dk/linux.config. This is the file that contains the kernel configuration. We have prepared a custom kernel configuration to have a simple but working kernel configuration. Of course, it can be adjusted to your needs, as we will demonstrate in the next blog post.
  • We enabled the option Build a Device Tree Blob (DTB) and set In-tree Device Tree Source file names to stm32mp157c-dk2. This tells Buildroot to build and install the Device Tree Blob that matches our hardware platform.
  • Finally, we enabled Install kernel image to /boot in target, so that the kernel image and the Device Tree blob are installed inside the /boot directory in the root filesystem. Indeed, our U-Boot configuration will load them from here (see below).

In the Target packages menu, we have kept the default: only the BusyBox package is enabled. BusyBox is a very popular tool in the embedded Linux ecosystem: it provides a lightweight replacement for a Linux shell and most common Linux command line tools (cp, mv, ls, vi, wget, tar, and more). Our basic system in fact only contains BusyBox!

In the Filesystem images menu, we have enabled the ext2/3/4 root filesystem type and chosen the ext4 variant. As explained above, ext4 is kind of the de-facto standard Linux filesystem for block storage devices such as SD cards.

In the Bootloaders menu, we enabled U-Boot, where a significant number of options need to be tweaked:

  • We download U-Boot from a STMicroelectronics Git repository at https://github.com/STMicroelectronics/u-boot.git and use the Git tag v2018.11-stm32mp-r2.1.
  • This U-Boot comes with a pre-defined configuration called stm32mp15_basic, which we select using Board defconfig.
  • However, it turns out that this pre-defined configuration enables the STM32 watchdog, and since our Linux user-space does not have a watchdog daemon to tick the watchdog regularly, it would reset constantly. Using a small additional snippet of U-Boot configuration, stored in the file board/stmicroelectronics/stm32mp157-dk/uboot-fragment.config, we disable the watchdog. Of course, it should be re-enabled and properly handled in Linux user-space for a final product.
  • In the U-Boot binary format sub-menu, we tell Buildroot that the second stage bootloader image will be called u-boot.img, and this is the one Buildroot should install in output/images
  • We tell Buildroot that our U-Boot configuration will build a first stage bootloader called spl/u-boot-spl.stm32, which allows Buildroot to install it in output/images
  • Finally, we pass a custom DEVICE_TREE=stm32mp157c-dk2 option in the U-Boot environment, which is needed for the U-Boot build process to find the Device Tree used internally by U-Boot.

Finally, in the Host utilities menu, we enable the host genimage package.

This entire configuration is saved in a simple text file called configs/stm32mp157_dk_defconfig, which is the one we loaded initially when running make stm32mp157_dk_defconfig. We suggest you take a moment to look at configs/stm32mp157_dk_defconfig and see the configuration options it defines.

What happens during the Buildroot build?

With all these options in place, here is what Buildroot has done to build our system (we have omitted some intermediate steps or package dependencies for the sake of brievity):

  1. Download and install the pre-built ARM compiler from ARM’s website, and install the C and C++ libraries inside the target root filesystem
  2. Download the Linux kernel source code from STMicroelectronics Github repository, configure it with our configuration file, build it, install zImage and stm32mp157c-dk2.dtb both in output/images and in the target root filesystem in the /boot directory. It also installs the Linux kernel modules inside the target root filesystem
  3. Download the U-Boot source code from STMicroelectronics Github repository, configure it, build it and install u-boot-spl.stm32 and u-boot.img in output/images
  4. Download the Busybox source code from the project official website, configure it, build it and install it inside the target root filesystem.
  5. Copies the contents of the rootfs overlay inside the target root filesystem
  6. Produce the ext4 image of the root filesystem, and install it as output/images/rootfs.ext4
  7. Call the genimage.sh script, whose purpose is to generate the final SD card image, output/images/sdcard.img

Let’s now have a look at the file board/stmicroelectronics/stm32mp157-dk/genimage.cfg, which tells the genimage utility how to create the final SD card image:

image sdcard.img {
        hdimage {
                gpt = "true"
        }

        partition fsbl1 {
                image = "u-boot-spl.stm32"
        }

        partition fsbl2 {
                image = "u-boot-spl.stm32"
        }

        partition uboot {
                image = "u-boot.img"
        }

        partition rootfs {
                image = "rootfs.ext4"
                partition-type = 0x83
                bootable = "yes"
                size = 256M
        }
}

What this file says is:

  • We want to create a file named sdcard.img
  • This file will contain a number of partitions, described by a GPT partition table. This is necessary for the STM32MP157 built-in ROM code to find the first stage bootloader.
  • The first two partitions are named fsbl1 and fsbl2, and contain the raw binary of the first stage bootloader, i.e there is no filesystem in those partitions. it is the STM32MP157 built-in ROM code that is hardcoded to search the first stage bootloader in the first two partitions whose name start with fsbl.
  • The third partition named uboot contains the second stage bootloader, also as a raw binary (no filesystem). Indeed, the first stage bootloader is configured to search the second bootloader from the third partition of the SD card (this is defined in the U-Boot configuration and can be modified if needed)
  • The fourth partition contains the ext4 filesystem image that Buildroot has produced, which is in fact our Linux root filesystem, with BusyBox, the standard C/C++ libraries and the Linux kernel image and Device Tree Blob.

This last partition is marked bootable. This is important because the U-Boot configuration for the STM32MP157 hardware platform by default uses the U-Boot Generic Distro Concept. At boot, U-Boot will search for the partition marked bootable, and then inside the filesystem contained in this partition, look for the file /boot/extlinux/extlinux.conf to know how to boot the system.

This file extlinux.conf is inside our root filesystem overlay at board/stmicroelectronics/stm32mp157-dk/overlay/boot/extlinux/extlinux.conf, so it is installed in our root filesystem as /boot/extlinux/extlinux.conf so that U-Boot finds it. This file simply contains:

label stm32mp15-buildroot
  kernel /boot/zImage
  devicetree /boot/stm32mp157c-dk2.dtb
  append root=/dev/mmcblk0p4 rootwait

Which tells U-Boot that the kernel image to load is /boot/zImage, that the Device Tree Blob to use is /boot/stm32mp157c-dk2.dtb and that the string root=/dev/mmcblk0p4 rootwait must be passed as arguments to the Linux kernel when booting. The root=/dev/mmcblk0p4 is particularly important, because it is the one telling the Linux kernel where the root filesystem is located.

So, if we summarize the boot process of our hardware platform with those new details in mind, it looks like this:

  1. The STM32MP157 built-in ROM code looks for the GPT partitions whose name start with fsbl, and if one is found, loads the contents into the STM32 internal memory and runs it. This is our first stage bootloader.
  2. This first stage bootloader is hard-coded to load the second stage bootloader from the third partition of the SD card. So it initializes the external RAM, loads this second stage bootloader into external RAM and runs it.
  3. The second stage bootloader does some more initialization, and then looks for a partition marked bootable. It finds that the fourth partition is bootable. It loads the /boot/extlinux/extlinux.conf file, thanks to which it learns where the kernel and Device Tree are located. It loads both, and starts the kernel with the arguments also specified in the extlinux.conf file.
  4. The Linux kernel runs, up to the point where it mounts the root filesystem, whose location is indicated by the root=/dev/mmcblk0p4 argument. After mounting the root filesystem, the kernel starts the first user-space process.
  5. The first user-space process that runs is /sbin/init, implemented by BusyBox. It starts a small number of services, and then starts the login prompt.

Conclusion

You can find the exact Buildroot source code used to reproduce the system used in this article in the branch at 2019.02/stm32mp157-dk-blog-1.

In this long initial blog post, we have learned what Buildroot is, how to use it to build a basic system for the STM32MP157 platform, how the Buildroot configuration was created, and how the STM32MP157 platform is booting.

Stay tuned for the next blog post, during which we will learn how to plug an additional device to the board: a pressure, temperature and humdity sensor connected over I2C, and how to make it work with Linux.

Linux 5.1 released, Bootlin contributions

LinuxLinux 5.1 was released a a few days ago by Linus Torvalds. As usual, LWN covered the major new features of this release by looking at what got merged during the merge window: part 1 and part 2. KernelNewbies also has a nice summary.

Bootlin contributed 181 commits to this release, making us the 14th contributing company by number of commits. Taking the entire Git history of the kernel, Bootlin has contributed a total of 6256 patches, making us the 15th contributing company by number of commits: a demonstration of our long-term involvement in the upstream Linux kernel community.

For Linux 5.1, our significant contributions have been:

  • In the RTC subsystem
    • Alexandre Belloni contributed a new RTC driver for the RV3028 RTC
    • Alexandre Belloni, as the maintainer of the RTC subsystem, continued to contribute a number of fixes/improvements in various RTC drivers
  • As part of a customer project in which we ported a modern U-Boot and Linux to a custom NXP LPC3250 platform, Alexandre Belloni contributed a fix for the LPC3250 serial port driver, and Grégory Clement moved the LPC3250 Analog-to-Digital converter Device Tree binding out of staging. We have other patches/fixes related to LPC3250 in the pipeline.
  • In the support for Marvell platforms
    • Antoine Ténart contributed numerous improvements to the mvpp2 driver, especially to properly handle the reset state of the different hardware blocks depending on how a particular Ethernet port is configured.
    • Grégory Clement contributed a new cpufreq driver to support CPU frequency scaling on Marvell Armada 8K
    • Maxime Chevallier contributed numerous improvements to the marvell10g Ethernet PHY driver, mainly to support 2.5G and 5G speeds, and to support the 88E2110 PHY. He also enabled 2.5G support in the mvpp2 Ethernet MAC driver.
    • Miquèl Raynal continued his work to bring suspend/resume support to the Armada 37xx platform: he added support for the Armada 37xx COMPHY and Armada 37xx USB UTMI PHY, added suspend/resume in ehci-orion, improved the USB core code to use the generic PHY API, and did a number of related Device Tree changes.
  • In the support for RaspberryPi platforms
    • Boris Brezillon made some fixes in the vc4 display controller driver: support for X/Y reflection was added, negative X/Y positioning was fixed, and support for margins on HDMI displays was added.
  • In the support for Allwinner platforms
    • Maxime Ripard made a number of small improvements to the Allwinner display controller driver
    • Paul Kocialkowski contributed a number of improvements to the support of YUV planes in the Allwinner display controller driver, in relation to our work on the Allwinner VPU support.
    • Paul Kocialkowski contributed some Device Tree changes to enable the Allwinner VPU on Allwinner A10, as well as a few fixes for the Allwinner VPU driver itself.
    • As listed below, Boris Brezillon converted the Allwinner NAND controller driver to the exec_op interface.
  • After extending the generic PHY subsystem in Linux 5.0 with two new hooks phy_configure() and phy_validate(), Maxime Ripard used this extension to add a driver for the Cadence D-PHY (used in combination with Cadence MIPI DSI and CSI transceivers), to convert the Allwinner A31 DSI D-PHY handling to the generic PHY subsystem, and converted the Cadence DSI driver to use the generic PHY API.
  • In the MTD subsystem
    • The NAND controller driver for Allwinner platform was converted to the use the new exec_op interface by Boris Brezillon.
  • In the GPIO subsystem
    • Thomas Petazzoni added support in the core GPIO subsystem to enable pull-up/pull-down resistors available in some GPIO controllers. It was contributed together with an implementation for the PCA953x family of I2C GPIO expanders.

In addition to writing code and submitting patches, a number of Bootlin engineers are also maintainers of various areas in the Linux kernel. As part of their maintainer duties, they review and merge patches from other developers:

  • Maxime Ripard, as the Allwinner platform maintainer, merged 76 patches from other developers
  • Alexandre Belloni, as the RTC subsystem maintainer and Atmel/Microchip platform co-maintainer, merged 53 patches from other developers
  • Miquèl Raynal, as the NAND subsystem maintainer, merged 38 patches from other developers
  • Grégory Clement, as the Marvell platform maintainer, merged 18 patches from other developers

And finally, here is as usual the detailed list of each patch we contributed:

Embedded Linux system development course on STM32MP1 Discovery

Embedded Linux system developmentFor many years, Bootlin has been offering an Embedded Linux system development training course, which has been delivered world-wide to hundreds of engineers by Bootlin trainers. This course is the most appropriate one for engineers getting started with embedded Linux: it goes through all the software layers of an embedded Linux system, from the toolchain to the application, through the bootloader, Linux kernel and basic user-space. With numerous hands-on labs, attendees get practical experience during this training, and learn how to build their embedded Linux system from the ground-up.

This course has been available for a while in two variants:

  • A 5-day variant, which covers all topics, including flash storage and filesystems as well as-real time
  • A 4-day variant, which is identical to the 5-day variant, except that flash storage and filesystem and real-time are not covered

Embedded Linux system developmentToday, we are happy to announce that all the practical labs of our 4-day variant are now done on the recently announced STM32MP157 Discovery board, which uses the STM32MP157 processor from STMicroelectronics. This processor has a number of interesting features for a large number of embedded applications, as we discussed in a previous blog post.

Just like for all our training courses, the training materials for this course are publicly and freely available:

Bootlin trainers are available to deliver this course on-site anywhere in the world. See this page for more details.

STM32MP1 system-on-chip, Bootlin member of ST Partners program

Earlier this year at Embedded World, STMicroelectronics announced the release of their first MPU, the STM32MP1 system-on-chip. Bootlin has been selected as one of the companies offering engineering and training services to be part of the ST Partners program around this new platform. In this blog post, we will give more details about STM32MP1 and Bootlin’s initial efforts on this platform.

The STM32MP1 platform

For the past several years, STMicroelectronics has developed a range of 32-bit microcontrollers based on the ARM Cortex-M cores. The most high-end ones, based on Cortex-M4 and M7, were powerful enough to run a Linux operating system with external RAM attached, and ST has been very active in adding support for these micro-controllers in the upstream U-Boot and Linux projects. However, the Cortex-M4 and M7 being MMU-less processor cores, Linux could work only with a number of limitations, preventing from using some complex Linux software stacks.

Block diagram of the STM32MP157
Block diagram of the STM32MP157
With the STM32MP1, ST is now offering a full-featured microprocessor, based on the combination of one or two Cortex-A7 cores (650 Mhz), one Cortex-M4 core (209 Mhz) and a wide variety of peripherals. The STM32MP1 is currently available in 3 variants:

  • STM32MP151, featuring one Cortex-A7, one Cortex-M4, and the common set of peripherals
  • STM32MP153, featuring two Cortex-A7, one Cortex-M4, the common set of peripherals plus CAN-FD
  • STM32MP157, featuring two Cortex-A7, one Cortex-M4, the common set of peripherals plus a 3D GPU, a DSI display interface and CAN-FD

The hardware blocks integrated in the STM32MP1 offers a large amount of features and connectivity options:

  • External DDR controller, supporting up to LPDDR2/3 and DDR3
  • QuadSPI memory interface
  • NAND flash controller, with built-in ECC capability
  • 6 I2C controllers
  • 4 UARTs and 4 USARTs
  • 6 SPI controllers
  • 4 SAI audio interfaces
  • HDMI-CEC interface
  • 3 SD/MMC controllers
  • 2 CAN controllers
  • 2 USB host + 1 USB OTG controllers
  • 1 Gigabit Ethernet MAC
  • Camera interface (parallel)
  • 2 ADCs, 2 DACs, 1 digital filter for sigma delta modulators
  • LCD controller supporting up to 1366×768
  • GPU from Vivante (which means open-source support is available!)
  • MIPI DSI
  • Plenty of timers
  • Crypto accelerators, random number generator (only in the C variants of the SoC)
  • Secure boot (only in the C variants of the SoC)

This combination of a wide range of connectivity options, graphics support with GPU support, and a Cortex-M4 for real-time logic, makes the STM32MP1 interesting for a large number of applications.

Software support for the STM32MP1

Bootlin being a consulting company specialized in low-level Linux software for embedded platforms, it is obviously a key aspect we looked at for the STM32MP1 platform.

First of all, a number of hardware blocks used in the STM32MP1 platform were already used on previous micro-controllers from ST and were therefore already supported in upstream projects such as U-Boot and Linux. The fact that these micro-controller products can run upstream versions of U-Boot and Linux is a good indication of ST’s strategy in terms of upstream support.

Then, even before the STM32MP1 product was publicly announced, a significant number of ST engineers had already started contributing to upstream TF-A, U-Boot and Linux the support for various pieces needed for the STM32MP1. Even if the support is not entirely upstream at this point, this strategy of starting the upstreaming effort ahead of the product announcement is very good.

Even though the work towards open-source GPU support has tremendously progressed over the past years, GPUs were notoriously known for being difficult to support in a fully open-source software stack. It is interesting to see that ST has chosen the GPU from Vivante for this STM32MP1, as Vivante is one of the first embedded GPU supported by Mesa, the open-source OpenGL implementation. Vivante GPUs are already used in a number of other SoCs, especially from NXP, and the Vivante open-source support, called etnaviv has therefore already seen some significant usage in production.

Until all the support for the STM32MP1 is fully upstreamed, ST provides publicly available Git repositories for all pieces of the software stack:

In addition to the availability of the code, there is also plenty of documentation available in the Development zone of the STM32 MPU wiki.

Hardware platforms for the STM32MP1

ST provides a low-cost evaluation platform called Discovery, available in two versions:

  • STM32MP157A-DK1, which features the STM32MP157A processor (all features, but without secure boot), LEDs, push buttons, Ethernet, one USB-C connector, 4 USB-A connectors, HDMI, microSD, analog audio, Arduino and RPi compatible connectors. The cost is $69.
  • STM32MP157C-DK2 features the STM32MP157C processor (all features including secure boot), and has the same features as the DK1 variant, with the addition of a DSI panel with touch and a WiFi/Bluetooth chip. The cost is $99.
STM32MP157-DK2 board
STM32MP157-DK2 board

ST also provides some more feature-complete evaluation boards: the STM32MP157A-EV1 and STM32MP157C-EV1, which only differ by the lack or availability of secure boot support. They offer more hardware features than the Discovery platforms, and are obviously available at a higher cost, $399.

In addition to these platforms provided by ST, several manufacturers have already announced a number of boards or system-on-module based on the STM32MP1:

OSD32MP15x system-in-package
OSD32MP15x system-in-package

Bootlin member of ST’s partner program

Bootlin is proud to have been chosen by ST to be part of its partner program when the STM32MP1 platform was announced. As a software partner, Bootlin can offer its training and engineering services to customers using the STM32MP1. We can provide:

  • Engineering for the development of Linux Board Support Packages for STM32MP1 platforms: porting U-Boot, porting Linux, writing Linux device drivers, delivering a fully integrated and optimized Linux system generated with Yocto or Buildroot
  • Training on embedded Linux, Linux kernel development and Yocto usage around the STM32MP1 platform

Bootlin trainings on STM32MP1

As a ST partner, Bootlin will be porting two of its existing training courses to the STM32MP1 platform: this means that all the practical labs in those courses will take place on the STM23MP157 Discovery board. We will soon be announcing:

Of course, as Bootlin has always done, all the training materials will be made freely available, under the same Creative Commons license we already use for existing training materials.

Building a Linux system for STM32MP1

The STM32MP1 being the first micro-processor in this family of SoCs from ST, a number of companies will most likely migrate from a micro-controller environment to a micro-processor one. This means moving from a situation where only a bare-metal application or a simple RTOS is used, to a situation where a feature-rich operating system such as Linux is being used. This migration is not always trivial as it requires gaining a lot of knowledge about U-Boot, the Linux kernel, Linux system integration and development, and more.

In order to help with this, in addition to the training courses described above, we will soon start publishing a series of blog posts that describe step by step how to build a Linux system for the STM32MP157 Discovery Kit, all the way up to reading data from an I2C sensor, and displaying them in a Qt5 based application. Stay tuned on our blog for those articles in the next few weeks!

Buildroot training course updated: Buildroot 2019.02, BeagleBone Black Wireless

Buildroot logoBootlin has been for many years a key contributor to the Buildroot project, a very popular embedded Linux build system. A few years ago, we decided to share our Buildroot expertise by creating a corresponding training course: Embedded Linux development with Buildroot, for which the training materials are freely available, under a Creative Commons license.

We have recently updated this training course up to Buildroot 2019.02, which is the latest “long term support” release of the project. Both the lectures and practical labs have been updated to this Buildroot version.

BeagleBone Black WirelessIn addition, the board used in the course has been changed to the BeagleBone Black Wireless, instead of the BeagleBone Black, which is no longer easily available. The practical labs were updated accordingly, and we now use the USB device interface to provide network connectivity between the development PC and the embedded target.

This 3-day Buildroot training course can be delivered on-site at your location, anywhere in the world. See our cost and registration page for more details.

Feedback from the Netdev 0x13 conference

The Netdev 0x13 conference took place last week in Prague, Czech Republic. As we work on a variety of networking topics as part of our Linux kernel contributions, Bootlin engineers Maxime Chevallier and Antoine Ténart went to meet with the Linux networking community and to see a lot of interesting sessions. It’s the third time we enjoy attending the Netdev conference (after Netdev 2.1 and Netdev 2.2) and as always, it was a blast!

The 3-day conference started with a first day of workshops and tutorials. We enjoyed learning how to be the cool kids thanks to the XDP hands-on tutorial where Jesper Brouer and Toke Høiland-Jørgensen cooked us a number of lessons to progressively get to learn how to write and load XDP programs. This was the first trial-run of the tutorial which is meant to be extended and used as a material to go through the XDP basics. The instructions are all available on Github.

We then had the chance to attend the TC workshop where face to face discussions and presentations of the traffic control hot topics being worked on happened. The session caught our attention as the topic is related to current subjects being worked on at Bootlin.

Being used to work on embedded systems, seeing the problems the Network developers face can sometimes come as a surprise. During the TC workshop, Vlad Buslov presented his recent work on removing TC flower’s the dependency to the global rtnl lock, which is an issue when you have a million classification rules to update quickly.

We also went to the hardware offload workshop. The future of the network offload APIs and support in the Linux kernel was discussed, with various topics ranging from ASIC support to switchev advanced use-cases or offloading XDP. This was very interesting to us as we do work on various networking engines providing many offloading facilities to the kernel.

The next two days were a collection of talks presenting the recent advances in the networking subsystem of the Linux kernel, as well as current issues and real-world examples of recent functionalities being leveraged.

As always XDP was brought-up with a presentation of XDP offloading using virtio-net, recent advances in combining XDP and hardware offloading techniques and a feedback from Cloudflare using XDP in their DDOS mitigation in-house solution.

But we also got to see other topics, such as SO_TIMESTAMPING being used for performance analytics. In this talk Soheil Hassas Yeganeh presented how the kernel timestamping facilities can be used to track individual packets withing the networking stack for performance analysis and debugging. This was nice to see as we worked on enabling hardware timestamping in networking engines and PHYs for our clients.

Another hot topic this year was the QUIC protocol, which was presented in details in the very good QUIC tutorial by Jana Iyengar. Since this protocol is fairly new, it was brought-up in several sessions from a lot of interesting angles.

Although QUIC was not the main subject of Alissa Cooper’s keynote on Open Source, the IETF, and You, she explained how QUIC was an example of a protocol that is designed alongside its implementations, having a tight feedback loop between the protocol specifications and its usage in real-life. Alissa shared Jana’s point on how middle-boxes are a problem when designing and deploying new protocols, and explained that an approach to overcome this “ossification” is to encrypt the protocol header themselves and document the invariant parts of the non-encrypted parts.

A consequence of having a flexible protocol is that it is not meant to be implemented in the kernel. However, Maciej Machnikowski and Joshua Hay explained that it is still possible to offload some of the processing to hardware, which sparked interesting discussions with the audience on how to do so.

Conclusion

The Netdev 0x13 conference was well organized and very pleasant to attend. The content was deeply technical and allowed us to stay up-to-date with the latest developments. We also had interesting discussions and came back with lots of ideas to explore.

Thanks for organizing Netdev, we had an amazing time!