D
R
M
Based on DRM Framework
HDMI Hot Plug Process Analysis
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.
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.
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.
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.
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
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
GeDu Friends Official Account
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