Direct Rendering Manager (DRM) for dummies

Published on March 7, 2019

While the standard framebuffer device interface (fbdev) is lacking many features and might be deprecated any time now, the Direct Rendering Manager (DRM) is the trending display subsystem. That is the reason why the mainline kernel is using this very subsystem for i.MX6.

The purpose of this post is to share what we've learned testing the latest Linux kernel and provide resources for further investigation.

For the impatient

Pre-built images

TODO (debian?)

If you just want to boot up Buildroot with the mainline kernel, we’ve uploaded a basic image that will run on our BD-SL-i.MX6 (SABRE Lite), Nitrogen6x or Nitrogen6_Max board.

The image contains no Freescale-licensed content, so you can download the image directly from here:

20150526-nitrogen-mainline_4.1rc2-buildroot-drm.ext2.gz

Programming the image

TODO (debian?)

Buildroot generates partition files which requires the block device, SDCard or SATA drive, to be partitioned already. Here is a command that allows to create a full size partition to the device:

$ sudo parted -a optimal /dev/sdc mkpart primary 0% 100%

Once the device has at least one partition of 4GB, the pre-built image can be flashed:

$ sudo umount /dev/sdc* $ zcat 20150526*.ext2.gz | sudo dd of=/dev/sdc bs=1M $ sync

You can plug in the freshly imaged device to the board and power it up.

Note: The user login is root with no password.

What's inside?

Linux kernel

When writing this article, the latest Linux kernel version is 4.1-rc2 which is the base or our work. On top of it have been merged a couple of patches for display support in mainline:

  • https://patchwork.kernel.org/patch/6212451/
  • https://patchwork.kernel.org/patch/6439221/
  • https://patchwork.kernel.org/patch/6439231/
  • https://patchwork.kernel.org/patch/6466851/
  • https://patchwork.kernel.org/patch/6466871/

A specific 6x_bootscript has been written in order to boot mainline kernel:

https://github.com/boundarydevices/u-boot-imx6/blob/staging/board/boundary/nitrogen6x/6x_bootscript-mainline.txt

User-space apps

  • libdrm tests
    • modetest
    • modeprint
    • https://cgit.freedesktop.org/mesa/drm/tree/tests
  • David Herrmann's examples:
    • modeset
    • modeset-vsync 
    • https://github.com/dvdhrm/docs/tree/master/drm-howto

Architecture

DRM/KMS driver

As with most new technologies, it comes with a new set of jargon:

  • DRM (Direct Rendering Manager)
    • Originally created for 3D GPU management on Linux
    • Provides common support functions for card-specific drivers as a minimum set of IOCTL operations.
  • KMS (Kernel Mode Setting)
    • Component which is solely responsible for the mode setting
    • It is the device driver for a display controller (IPUv3 for i.MX6)
  •  Framebuffer
    • Standard object storing information about the content to be displayed
    • Default implementation available for GEM objects using CMA (Contiguous Memory Allocator)
      • GEM is a kernel module that provides a library of functions for managing memory buffers
      • TTM is another option which performs the same as GEM but is more complex and handles on-card memory
  • Planes
    • Image layer (cursor or overlay for instance)
  • CRTC
    • Configure the appropriate display settings
    • Scan out frame buffer content to one or more encoders
  • Encoder
    • Responsible for converting a frame into the appropriate format
  • Connector
    • Represent a display connector (HDMI, DP, VGA, DVI, ...)
    • Detect display connection/removal
    • Expose display supported modes

device_model_pinchart

 

Laurent Pinchart's ELC 2013 presentation

In order to understand those different parts, here is their equivalent on Nitrogen6x:

  • 3 connectors: HDMI, RGB and LVDS
  • 3 encoders (1 per connector): IPUv3
  • 4 CRTC: IPU1 & IPU2 both have two possible inputs

User-space layers

Every user-space layer is based on libdrm, which is a C library from freedesktop.org (and bundled with all distributions), which exposes a stable API. This library is then used from X, Mesa (opengl) drivers, libva, etc. When features are added to drm drivers (eg new ioctls), a corresponding release of libdrm is made. 

1200px-High_level_Overview_of_DRM.svg

 

The API offered by libdrm can be found here:

https://cgit.freedesktop.org/mesa/drm/tree/xf86drm.h#n570

https://cgit.freedesktop.org/mesa/drm/tree/xf86drmMode.h#n338

libdrm test applications allow to get lots of info from the hardware:

# modeprint imx-drm Resources count_connectors : 3 count_encoders : 3 count_crtcs : 4 count_fbs : 0 Connector: VGA-1 id : 32 encoder id : 31 conn : connected size : 0x0 (mm) count_modes : 1 count_props : 2 props : 1 2 count_encoders : 1 encoders : 31 Mode: "800x480" 800x480 62 Connector: HDMI-A-1 id : 34 encoder id : 33 conn : disconnected size : 0x0 (mm) count_modes : 0 count_props : 2 props : 1 2 count_encoders : 1 encoders : 33 Connector: LVDS-1 id : 36 encoder id : 0 conn : connected size : 203x152 (mm) count_modes : 1 count_props : 2 props : 1 2 count_encoders : 1 encoders : 35 Mode: "1024x768" 1024x768 60 ...

What does it change?

Framebuffer access

Although FBDEV helper nodes are possible, the CONFIG_DRM_IMX_FB_HELPER seems to be problematic in dual-display setup. But that doesn't change the fact that the framebuffer is held on a contiguous memory buffer. However in order to retrieve its address, the /dev/fbx can be considered as outdated in favour of DRM_IOCTL_MODE_CREATE_DUMB ioctl from the DRM node (/dev/dri/card0).

See David's example that is perfect to understand how to create your own framebuffer:

https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c#L439

Using page-flip capability

Once again David's example is the best source to understand and develop your own page-flip example:

https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset-vsync.c

But in order to witness the benefits of the page flip, the best is to try out the standard double buffered example first:

# modeset-double-buffered

As this example isn't using the page-flip, some tearing can be seen on the display. Then the exact same example with the page-flip feature will look flawless:

# modeset-vsync

Multi-display support

If more than one display is available, first make sure the output of 'modeprint imx-drm' list all of them as connected. Then you can try to display a pattern on each and every display. Here are the commands to be used in case of the Nitrogen6x:

# modetest -M imx-drm -s 32:800x480 # modetest -M imx-drm -s 34:1920x1080 # modetest -M imx-drm -s 36:1024x768

If you want to display on all the displays at once, the modeset binary should work just fine. To be noted again that CONFIG_DRM_IMX_FB_HELPER proved to be an issue and should be removed from the kernel configuration.

If you are using X, please see next sub-section.

X configuration

By default, with no particular configuration, the X server is trying to open /dev/dri/card0 and runs on the first available display. In case several displays are connected to the target, X will try to have them cloned:

The ultimate tool to get and modify the X display configuration is xrandr. This latter allows you to mode-set the way you want organize your display.

For instance, if you have one HDMI 1080p display and one LVDS 1024x768 that you want to set as follows:

(0,0)-----------------+ | |(1920,312)---+ | 1920 x 1080 || | | HDMI-0 || 1024 x 768 | | || LVDS-0 | +---------------------++------------+

You can use xrandr once the X server is up and running.

# xrandr --output HDMI-0 --left-of LVDS-0

 

Or you can create a /etc/X11/xorg.conf for the X server to always set the displays as above.

Section "Monitor" Identifier "HDMI-0" Option "PreferredMode" "1920x1080" Option "Position" "0 0" EndSection Section "Monitor" Identifier "LVDS-0" Option "PreferredMode" "1024x768" Option "Position" "1920 312" EndSection

 

HDMI EDID override

The DRM driver allows you to override the EDID for an HDMI display detection through the command-line. But first you need to add CONFIG_DRM_LOAD_EDID_FIRMWARE to your kernel configuration. Then  the path of the EDID blob, which must be located under /lib/firmware/, can be specified via the "drm_kms_helper.edid_firmware" argument.

To ease the use of this capability, the 6x_bootscript is automatically adding the argument is u-boot variable force_edid contains the path of the blob:

https://github.com/boundarydevices/u-boot-imx6/blob/staging/board/boundary/nitrogen6x/6x_bootscript-mainline.txt#L39

Finally, in order to create your own EDID blob, kernel documentation gives a pretty good example:

https://lxr.free-electrons.com/source/Documentation/EDID/

Kernel command-line arguments

As displays such as RGB or LVDS cannot dynamically detected from the kernel (no EDID), they are considered connected unless told otherwise from the kernel command-line.

That is why the 6x_bootscript modify the kernel command-line in order to inform the kernel of disconnected display:

What about Gstreamer?

Well we haven't found any good example of a sink that would use the DRM card directly without going through a FBDEV. Then the obvious easy solution is to have a X server running to be able to use the ximagesink.

# gst-launch-1.0 videotestsrc ! ximagesink display=":0"

What about Qt5?

It seems that an experimental plugin exists for Qt to rely on KMS but we haven't had time to test it yet:

https://doc.qt.io/qt-5/embedded-linux.html#kms

References

https://en.wikipedia.org/wiki/Direct_Rendering_Manager

https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api

https://dvdhrm.wordpress.com/2012/12/21/advanced-drm-mode-setting-api

https://dri.freedesktop.org/wiki/DRM

https://free-electrons.com/kerneldoc/latest/DocBook/drm/

brezillon-drm-kms.pdf

KMS_FB_and_V4L2_How_to_Select_a_Graphics_and_Video_API.pdf

Elc2013_Pinchart.pdf