Analysis of HDMI Hot Plug Process Based on DRM Framework

Analysis of HDMI Hot Plug Process Based on DRM Framework

D

R

M

Based on DRM Framework

HDMI Hot Plug Process Analysis

Analysis of HDMI Hot Plug Process Based on DRM Framework

PART.01

D

R

Introduction

DRM stands for Direct Rendering Manager, which manages display output, buffer allocation, and frame buffering. The corresponding userspace library is libdrm, which provides a series of user-friendly control encapsulations, allowing users to easily control displays and request buffers.

GDK8 uses the Rockchip RK3328 chip, and Rockchip’s display framework consists of two main modules: DRM and FB. The FB framework corresponds to the 3.x kernel, while the DRM framework corresponds to the 4.x kernel. Therefore, GDK8, which uses the 4.19.161 kernel, adopts the HDMI based on the DRM display framework.

The device node for DRM is “/dev/dri/cardX”, where X is a value from 0 to 15, with the default being /dev/dri/card0.

Analysis of HDMI Hot Plug Process Based on DRM Framework

CRTC

Display controller, which is an abstraction of the SOC internal VOP (some documents also refer to it as LCDC) module on the Rockchip platform.

Plane

Layer, which is an abstraction of the SOC internal VOP (LCDC) module’s window layer on the Rockchip platform.

Encoder

Output converter, referring to display interfaces such as RGB, LVDS, DSI, eDP, HDMI, CVBS, VGA, etc.

Connector

Connector, referring to the interface part that interacts between the encoder and the panel.

Bridge

Bridging device, generally used for registering an encoder followed by another conversion chip, such as a DSI to HDMI conversion chip.

Panel

Generally refers to screens, an abstraction of various LCD, HDMI, and other display devices.

GEM

Buffer management and allocation, similar to ion in Android.

PART.02

Hot

Plug

Introduction

Hot plugging refers to the ability to insert and remove external devices without shutting down the system, without affecting the normal operation of the system. This feature is very important for many peripherals, as you would not want to have to shut down the system to insert an HDMI cable and then power it back on to make HDMI work again.

The process of handling hot plugging in the DRM driver is quite complex. Below, we will set breakpoints on some key functions and analyze their roles.

Analysis of HDMI Hot Plug Process Based on DRM Framework

PART.03

Hot

Plug

Process

#3.1 Update Physical Layer Status

When the kernel detects an HDMI hot plug event, it first changes the HDMI PHY status through the dw_hdmi_phy_update_hpd function.

void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,          bool force, bool disabled, bool rxsense){  u8 old_mask = hdmi->phy_mask;  if (force || disabled || !rxsense)    hdmi->phy_mask |= HDMI_PHY_RX_SENSE;  else    hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE;  if (old_mask != hdmi->phy_mask)    hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);}EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);

In userspace, the HDMI status information can be viewed through the virtual files in the dw-hdmi directory.

cat /sys/kernel/debug/dw-hdmi/statusPHY: enabled                    Mode: HDMIPixel Clk: 297000000Hz          TMDS Clk: 297000000HzColor Format: YUV444            Color Depth: 8 bitColorimetry: ITU.BT709          EOTF: Off

Mode

Current output mode

Pixel Clk

Current output pixel clock

TMDS Clk

Current output HDMI symbol rate

Color Format

Current output color format

Color Depth

Current output color depth

Colorimetry

Current output color standard

EOTF

HDR information

Through stack backtrace, it can be seen that when the kernel detects HDMI plug/unplug, it will detect the interrupt event and create a new thread to handle the event. At this time, the kernel will call dw_hdmi_irq, which will update the HDMI PHY and rxsense status.

kn # Child-SP          RetAddr           Call Site00 ffffff80`0a9b3d20 ffffff80`087f17d8 lk!dw_hdmi_phy_update_hpd [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 1730]01 ffffff80`0a9b3d20 ffffff80`087f1a1c lk!dw_hdmi_setup_rx_sense+0x78 [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 3178]02 ffffff80`0a9b3d20 ffffff80`08129238 lk!dw_hdmi_irq+0x22c [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 3219]03 ffffff80`0a9b3d20 ffffff80`08129618 lk!irq_thread_fn+0x28 [kernel/irq/manage.c @ 1010]04 ffffff80`0a9b3d20 ffffff80`080e168c lk!irq_thread+0x118 [kernel/irq/manage.c @ 1091]05 ffffff80`0a9b3d20 00000008`00000008 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]

#3.2 Check Connector Status

The DRM driver checks the status of each connector through the drm_helper_hpd_irq_event function. From the stack backtrace below, it can be seen that when HDMI is inserted, the kernel will also detect the interrupt event and create a new thread to handle the event. After updating the PHY and rxsense status, since it is a hot plug event, the repo_hpd_event function will be called (hpd: hot plug detect).

kn # Child-SP          RetAddr           Call Site00 ffffff80`0abd3d60 ffffff80`087f241c lk!drm_helper_hpd_irq_event [drivers/gpu/drm/drm_probe_helper.c @ 773]01 ffffff80`0abd3d60 ffffff80`080db1b8 lk!repo_hpd_event+0x8c [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 383]02 ffffff80`0abd3d60 ffffff80`080db47c lk!process_one_work+0x1a0 [./arch/arm64/include/asm/jump_label.h @ 31]03 ffffff80`0abd3d60 ffffff80`080e168c lk!worker_thread+0x4c [./include/linux/compiler.h @ 193]04 ffffff80`0abd3d60 ffffff80`08085dd0 lk!kthread+0x12c [kernel/kthread.c @ 260]05 ffffff80`0abd3d60 00000008`00000008 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]

In the repo_hpd_event function, the rxsense is set based on the PHY status. If a device is detected, the drm_helper_hpd_irq_event will be called.

static void repo_hpd_event(struct work_struct *p_work){  struct dw_hdmi *hdmi = container_of(p_work, struct dw_hdmi, work.work);  u8 phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0);  mutex_lock(&hdmi->mutex);  if (!(phy_stat & HDMI_PHY_RX_SENSE))    hdmi->rxsense = false;  if (phy_stat & HDMI_PHY_HPD)    hdmi->rxsense = true;  mutex_unlock(&hdmi->mutex);  if (hdmi->bridge.dev) {    bool change;    change = drm_helper_hpd_irq_event(hdmi->bridge.dev);#ifdef CONFIG_CEC_NOTIFIER    if (change)      cec_notifier_repo_cec_hpd(hdmi->cec_notifier,              hdmi->hpd_state,              ktime_get());#endif  }}

The drm_helper_hpd_irq_event function mainly does two things: modifies the connector’s status and decides whether to notify userspace to perform corresponding actions.

bool drm_helper_hpd_irq_event(struct drm_device *dev){  struct drm_connector *connector;  struct drm_connector_list_iter conn_iter;  enum drm_connector_status old_status;  bool changed = false;  if (!dev->mode_config.poll_enabled)    return false;  mutex_lock(&dev->mode_config.mutex);  drm_connector_list_iter_begin(dev, &conn_iter);  drm_for_each_connector_iter(connector, &conn_iter) {    /* Only handle HPD capable connectors. */    if (!(connector->polled & DRM_CONNECTOR_POLL_HPD))      continue;    old_status = connector->status;    connector->status = drm_helper_probe_detect(connector, NULL, false);    DRM_DEBUG_KMS="[CONNECTOR:%d:%s] status updated from %s to %s\n",            connector->base.id,            connector->name,            drm_get_connector_status_name(old_status),            drm_get_connector_status_name(connector->status));    if (old_status != connector->status)      changed = true;  }  drm_connector_list_iter_end(&conn_iter);  mutex_unlock(&dev->mode_config.mutex);  if (changed)    drm_kms_helper_hotplug_event(dev);  return changed;}

#3.3 Trigger KMS Event

If a status change is detected, drm_helper_hpd_irq_event will call drm_kms_helper_hotplug_event to trigger a KMS (kernel mode setting) event. The drm_sysfs_hotplug_event sends a uevent to the userspace udev process through the kobject_uevent_env function, while drm_client_dev_hotplug notifies the client of the hot plug event.

void drm_kms_helper_hotplug_event(struct drm_device *dev){  /* send a uevent + call fbdev */  drm_sysfs_hotplug_event(dev);  if (dev->mode_config.funcs->output_poll_changed)    dev->mode_config.funcs->output_poll_changed(dev);  drm_client_dev_hotplug(dev);}

#3.4 Transfer to Userspace

In the Ubuntu userspace, there are two main programs that serve the DRM driver: the udevd daemon responsible for managing devices and the graphical program Xorg.

3.4.1

Device Management Tool – udevd

In Linux, the underlying support for devices is handled by the kernel, but their related events are managed in userspace by the udevd process. As seen in the above drm_kms_helper_hotplug_event, the kernel sends a uevent to udevd through kobject_uevent_env. After receiving the uevent, udevd executes corresponding actions through userspace script files.

139 ?        00:00:00 systemd-udevd

The rules files managed by udevd are usually located in /etc/udev/rules.d/ or /usr/lib/udev/rules.d directories, with a .rules file extension.

file /etc/udev/rules.d/60-drm.rules/etc/udev/rules.d/60-drm.rules: ASCII textcat /etc/udev/rules.d/60-drm.rulesSUBSYSTEM=="drm", ACTION=="change", ENV{HOTPLUG}=="1", RUN+="/usr/local/bin/drm-hotplug.sh"

From the above 60-drm.rules file, we can see that after a hot plug event occurs, the drm-hotplug.sh script file will be executed, which will change the display status through xrandr.

cat /usr/local/bin/drm-hotplug.sh#!/bin/sh -x
# Try to figure out XAUTHORITY and DISPLAYfor pid in $(pgrep X 2>/dev/null || ls /proc|grep -ow "[0-9]*"|sort -rn); do    PROC_DIR=/proc/$pid
    # Filter out non-X processes    readlink $PROC_DIR/exe|grep -qwE "X$|Xorg$" || continue
    # Parse auth file and display from cmd args    export XAUTHORITY=$(cat $PROC_DIR/cmdline|tr '
' '
'| 
        grep -w "-auth" -A 1|tail -1)    export DISPLAY=$(cat $PROC_DIR/cmdline|tr '
' '
'| 
        grep -w "^:.*" || echo ":0")
    echo Found auth: $XAUTHORITY for dpy: $DISPLAY    breakdone
export DISPLAY=${DISPLAY:-:0}
# Find an authorized userunset USERfor user in root $(users);do    sudo -u $user xdpyinfo >/dev/null && 
        { USER=$user; break; }done[ $USER ] || exit 0
# Find disconnected monitorsMONITORS=$(sudo -u $user xrandr|grep -w disconnected|cut -d' ' -f1)
# Make sure every disconnected monitors been disabled especially DP-1.for monitor in $MONITORS;do    sudo -u $user xrandr --output $monitor --offdone
# Find connected monitorsMONITORS=$(sudo -u $user xrandr|grep -w connected|cut -d' ' -f1)
# Make sure every connected monitors been enabled with a valid mode.for monitor in $MONITORS;do    # Unlike the drm driver, X11 modesetting drv uses HDMI for HDMI-A    CRTC=$(echo $monitor|sed "s/HDMI\(-[^B]\)/HDMI-A\1/")
    SYS="/sys/class/drm/card*-$CRTC/"
    # Already got a valid mode    grep -w "$(cat $SYS/mode)" $SYS/modes >&& continue
    # Ether disabled or wrongly configured    sudo -u $user xrandr --output $monitor --autodone
exit 0

3.4.2

Graphical Program – Xorg

When the drm_client_dev_hotplug triggers a hot plug event through the Xorg client, Xorg will update the display information (such as composite layers, filling buffers) and assist the DRM driver in processing display information through the drmIoctl in the libdrm library. First, Xorg will obtain the display device information through RROutputSetCrtcs.

BoolRROutputSetCrtcs(RROutputPtr output, RRCrtcPtr * crtcs, int numCrtcs){    RRCrtcPtr *newCrtcs;    int i;
    if (numCrtcs == output->numCrtcs) {        for (i = 0; i < numCrtcs; i++)            if (output->crtcs[i] != crtcs[i])                break;        if (i == numCrtcs)            return TRUE;    }    if (numCrtcs) {        newCrtcs = xallocarray(numCrtcs, sizeof(RRCrtcPtr));        if (!newCrtcs)            return FALSE;    }    else        newCrtcs = NULL;    free(output->crtcs);    memcpy(newCrtcs, crtcs, numCrtcs * sizeof(RRCrtcPtr));    output->crtcs = newCrtcs;    output->numCrtcs = numCrtcs;    RROutputChanged(output, TRUE);    return TRUE;}

Then, it will decide whether to display the graphical interface through the RRCrtcSet function.

BoolRRCrtcSet(RRCrtcPtr crtc,          RRModePtr mode,          int x,          int y, Rotation rotation, int numOutputs, RROutputPtr * outputs){    ScreenPtr pScreen = crtc->pScreen;    Bool ret = FALSE;    Bool recompute = TRUE;    Bool crtcChanged;    int  o;
    rrScrPriv(pScreen);
    crtcChanged = FALSE;    for (o = 0; o < numOutputs; o++) {        if (outputs[o] && outputs[o]->crtc != crtc) {            crtcChanged = TRUE;            break;        }    }
    /* See if nothing changed */    if (crtc->mode == mode &&        crtc->x == x &&        crtc->y == y &&        crtc->rotation == rotation &&        crtc->numOutputs == numOutputs &&        !memcmp(crtc->outputs, outputs, numOutputs * sizeof(RROutputPtr)) &&        !RRCrtcPendingProperties(crtc) && !RRCrtcPendingTransform(crtc) &&        !crtcChanged) {        recompute = FALSE;        ret = TRUE;    }    else {        if (pScreen->isGPU) {            ScreenPtr master = pScreen->current_master;            int width = 0, height = 0;
            if (mode) {                width = mode->mode.width;                height = mode->mode.height;            }            ret = rrCheckPixmapBounding(master, crtc,                                        rotation, x, y, width, height);            if (!ret)                return FALSE;
            if (pScreen->current_master) {                Bool sync = rrGetPixmapSharingSyncProp(numOutputs, outputs);                ret = rrSetupPixmapSharing(crtc, width, height,                                           x, y, rotation, sync,                                           numOutputs, outputs);            }        }#if RANDR_12_INTERFACE        if (pScrPriv->rrCrtcSet) {            ret = (*pScrPriv->rrCrtcSet) (pScreen, crtc, mode, x, y,                                          rotation, numOutputs, outputs);        }        else#endif        {#if RANDR_10_INTERFACE            if (pScrPriv->rrSetConfig) {                RRScreenSize size;                RRScreenRate rate;
                if (!mode) {                    RRCrtcNotify(crtc, NULL, x, y, rotation, NULL, 0, NULL);                    ret = TRUE;                }                else {                    size.width = mode->mode.width;                    size.height = mode->mode.height;                    if (outputs[0]->mmWidth && outputs[0]->mmHeight) {                        size.mmWidth = outputs[0]->mmWidth;                        size.mmHeight = outputs[0]->mmHeight;                    }                    else {                        size.mmWidth = pScreen->mmWidth;                        size.mmHeight = pScreen->mmHeight;                    }                    size.nRates = 1;                    rate.rate = RRVerticalRefresh(&mode->mode);                    size.pRates = &rate;                    ret =                        (*pScrPriv->rrSetConfig) (pScreen, rotation, rate.rate,                                                  &size);                    /*                     * Old 1.0 interface tied screen size to mode size                     */                    if (ret) {                        RRCrtcNotify(crtc, mode, x, y, rotation, NULL, 1,                                     outputs);                        RRScreenSizeNotify(pScreen);                    }                }            }#endif        }        if (ret) {            RRTellChanged(pScreen);            for (o = 0; o < numOutputs; o++)                RRPostPendingProperties(outputs[o]);        }    }
    if (recompute)        RRComputeContiguity(pScreen);
    return ret;}

PART.04

Experimental

Environment

Hardware Device

GDK8 + Nano Target Probe

Software

Nano Code

GDK8 (GEDU Development Kit 8) is a development and debugging platform developed by GeDu Technology for the ARMv8 platform, pre-installed with the GNU development toolchain, allowing various ARM application software and driver development directly on the ARM system, fundamentally changing the traditional cross-compilation method and greatly improving development and debugging efficiency. GDK8 can work with GeDu Technology’s Nano Debugger (NDB), using various debugging commands from WinDBG to debug Linux programs, elevating debugging technology on the Linux platform to a new level, making it a powerful assistant for learning and researching ARMv8 architecture, LINUX operating system, and embedded software technology.

Analysis of HDMI Hot Plug Process Based on DRM Framework

Nano Target Probe (NTP) is a hardware debugger based on ARM CoreSight technology, whose core function is to access the target system through the CoreSight protocol, enabling system debugging and tuning.

Analysis of HDMI Hot Plug Process Based on DRM Framework

Nano Target Probe User Guide:

https://www.nanocode.cn/wiki/docs/gedu_ntp_wiki

Nano Code Download Link:

https://www.nanocode.cn/#/download

PART.05

GeDu

Academy

Introduction

Analysis of HDMI Hot Plug Process Based on DRM Framework

GeDu Academy is a knowledge sharing platform under GeDu Technology, based on the educational philosophy of “GeWu ZhiZhi” (to understand the principles of things).

In the spirit of inheriting the teachings of the sages, GeDu Academy strives to closely integrate the essence of traditional culture with modern technology, using traditional culture and humanistic feelings to explain modern technology, and using modern technology to disseminate traditional culture.

Access Methods

Mobile: Search “GeDu Academy” in WeChat Mini Program

Desktop: Download the Nano Code Community Edition client

https://www.nanocode.cn/#/download

Analysis of HDMI Hot Plug Process Based on DRM Framework

GeDu Friends Official Account

Analysis of HDMI Hot Plug Process Based on DRM Framework

GeDu Academy Mini Program

Previous Recommendations

This mechanism is impressive

Starting over brings many questions

The first instruction of the M core

The history and present of Harvard architecture

Analysis of HDMI Hot Plug Process Based on DRM Framework

Leave a Comment