第10篇:屏ESD Check内核逻辑驱动代码完整拆解

简介: 本文深度拆解MIPI屏ESD检测机制:基于Linux DRM框架,详解ESD Check原理、内核线程实现、竞态防护、复位流程及典型坑点,提供可落地的驱动代码与调试方案。

前言

做Android/Linux显示驱动,MIPI屏ESD问题是高频现场bug。静电干扰会导致MIPI链路异常、面板寄存器错乱、黑屏花屏闪屏。很多Panel驱动里都会实现ESD Check(静电检测)机制,定时去读取Panel寄存器、DDIC关于ESD相关的中断或者某个GPIO脚的电平变化,判断屏幕是否异常,触发软复位/硬复位恢复屏幕。

很多同学只知道驱动里有个ESD检测线程,但不清楚:

  1. ESD Check的底层原理是什么?
  2. 在内核DRM panel驱动中放在什么位置?
  3. 检测线程如何创建、启停、休眠?
  4. 读寄存器失败为什么就能判定ESD异常?
  5. 完整的代码逻辑怎么写,有哪些坑?

本文编者结合Linux DRM Panel开源驱动框架,完整拆解ESD Check。

一、ESD Check 原理

1. 静电对MIPI屏的影响

静电(ESD)冲击面板或者MIPI走线后,会出现几种现象:

  • MIPI DSI链路不稳定,Host和Panel通信断连
  • Panel内部寄存器值被篡改
  • 面板逻辑卡死,不再刷新画面,表现黑屏/花屏/闪屏硬件本身无法自动恢复,需要软件检测到异常,执行复位流程。

2. ESD Check核心思路

定期轮询读取Panel的状态寄存器(一般是Panel的ID寄存器、状态寄存器,读这个寄存器不需要改变屏幕显示):

  1. 定时发起DSI读指令,读取Panel指定寄存器
  2. 判断读取返回值: 读取成功,数据符合预期 > 屏幕正常,继续等待下一轮检测 ;读取失败 / 返回数据异常 > 判断发生ESD干扰
  3. 触发ESD恢复流程:Panel复位(断电时序+上电时序+重新发送初始化序列),恢复屏幕

为什么读寄存器可以判断ESD? 正常工作的Panel,ID寄存器是固定值。静电干扰后,DSI传输出错,要么读超时,要么读到乱码。

另外,ESD Check不能100%捕获所有静电问题,只能检测Panel通信链路异常,属于软件兜底方案,不能替代硬件ESD防护。

3. 触发时机

  • 屏幕已经点亮之后才启动ESD检测线程;
  • 屏幕suspend休眠时,停止ESD线程;resume唤醒后,重新开启线程。不能在屏没点亮时跑ESD检测,会造成无效DSI读写、内核报错。

二、内核架构:ESD Check放在哪里?

当前主流Linux DRM框架下,ESD检测逻辑一般封装在drm_panel驱动内(panel-xxx.c)。

  1. kthread 内核线程:ESD轮询任务主体,循环休眠 → 读寄存器 → 判断异常
  2. dsi host 接口:dsi_host_transfer,发送MIPI读寄存器指令
  3. work/workqueue(**其他**方案):部分老方案用延时工作队列,但是workqueue容易被其他任务抢占;长时间轮询ESD场景优先用kthread
  4. drm_panel ops:panel prepare/unprepare,屏上电下电,ESD异常复位时调用
  5. flag控制变量:esd_enable / esd_running,用来控制线程启停,防止竞态

两种实现方案对比

  • kthread:可控性强,可以长时间sleep,适合周期性轮询ESD;异常复位过程可以在线程内串行执行
  • delayed_work:简单,但复位流程较长时容易出现work重入,需要加锁保护

存在的竞态场景

  1. ESD 线程正在执行 Panel 复位(unprepare/prepare),同时系统触发 suspend 休眠,两套流程同时操作 Panel 电源 / MIPI;
  2. ESD 连续多次触发复位,多次上下电并发;
  3. 线程退出和 ESD 复位同时执行,访问野指针。

解决方案:

  • mutex互斥锁:保护 Panel 上电 / 下电 / ESD 复位临界区;
  • 状态标记esd_thread_run:安全控制线程循环退出;
  • 增加panel_in_recovery标志,防止 ESD 连续重复触发复位;
  • 日志分级:区分 dbg 调试日志、info 正常打印、warn 告警、error 错误,量产的user版本可关闭 debug 日志,减少 log 刷屏,userdebug版本可默认打开。

三、**code**示例

#include <drm/drm_panel.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/dev_printk.h>
#include <video/mipi_display.h>
// Panel私有结构体,ESD相关成员增加竞态保护标记
struct my_panel_data {
   
    struct drm_panel base;
    struct mipi_dsi_device *dsi;
    /* ESD Check */
    struct task_struct *esd_thread;
    struct mutex esd_lock;          // ESD临界区互斥锁
    bool esd_enable;
    bool esd_thread_run;
    bool panel_in_recovery;         // 标记:正在执行ESD复位,防止重入
    unsigned int esd_poll_interval; // 轮询周期 ms
    unsigned int esd_max_retry;     // 最大连续重试次数,防止死循环复位
    u8 check_reg;
    u8 expect_val;
};
static inline struct my_panel_data *to_my_panel(struct drm_panel *panel)
{
   
    return container_of(panel, struct my_panel_data, base);
}
/**
 * my_panel_esd_read_reg - DSI DCS读Panel寄存器
 * @ctx: panel私有数据
 * @reg: 待读取寄存器地址
 * @val: 读出的值输出
 * Return: 0成功,负数失败
 */
static int my_panel_esd_read_reg(struct my_panel_data *ctx, u8 reg, u8 *val)
{
   
    struct mipi_dsi_msg msg;
    ssize_t ret;
    u8 buf[1];
    memset(&msg, 0, sizeof(msg));
    msg.channel = ctx->dsi->channel;
    msg.type = MIPI_DSI_DCS_READ;
    msg.tx_buf = &reg;
    msg.tx_len = 1;
    msg.rx_buf = buf;
    msg.rx_len = 1;
    ret = mipi_dsi_transfer(ctx->dsi, &msg);
    if (ret < 0) {
   
        dev_dbg(&ctx->dsi->dev, "ESD read reg 0x%02x failed, ret:%zd\n", reg, ret);
        return ret;
    }
    *val = buf[0];
    dev_dbg(&ctx->dsi->dev, "ESD read reg 0x%02x val:0x%02x\n", reg, *val);
    return 0;
}
/**
 * my_panel_esd_recover - ESD异常复位恢复流程
 * 加锁保护,防止suspend与ESD复位并发操作panel
 */
static void my_panel_esd_recover(struct my_panel_data *ctx)
{
   
    mutex_lock(&ctx->esd_lock);
    if (ctx->panel_in_recovery) {
   
        dev_warn(&ctx->dsi->dev, "ESD recover already in progress, skip\n");
        mutex_unlock(&ctx->esd_lock);
        return;
    }
    ctx->panel_in_recovery = true;
    dev_info(&ctx->dsi->dev, "ESD detected, start panel recovery\n");
    // 执行屏下电
    drm_panel_unprepare(&ctx->base);
    msleep(100);
    // 重新上电+发送panel初始化序列
    drm_panel_prepare(&ctx->base);
    ctx->panel_in_recovery = false;
    mutex_unlock(&ctx->esd_lock);
    dev_info(&ctx->dsi->dev, "ESD recovery complete\n");
}
/**
 * ESD内核线程主循环
 */
static int my_panel_esd_thread(void *data)
{
   
    struct my_panel_data *ctx = data;
    u8 read_val;
    int ret;
    unsigned int fail_cnt = 0;
    dev_info(&ctx->dsi->dev, "ESD thread start\n");
    while (ctx->esd_thread_run) {
   
        msleep(ctx->esd_poll_interval);
        if (!ctx->esd_thread_run)
            break;
        if (!ctx->esd_enable)
            continue;
        ret = my_panel_esd_read_reg(ctx, ctx->check_reg, &read_val);
        if (ret != 0 || read_val != ctx->expect_val) {
   
            fail_cnt++;
            dev_warn(&ctx->dsi->dev,
                "ESD abnormal! reg:0x%02x read:0x%02x expect:0x%02x, fail_cnt:%u\n",
                ctx->check_reg, read_val, ctx->expect_val, fail_cnt);
            // 超过最大重试次数不再反复复位,防止死循环
            if (fail_cnt >= ctx->esd_max_retry) {
   
                dev_err(&ctx->dsi->dev, "ESD exceed max retry, stop auto recovery\n");
                fail_cnt = 0;
                continue;
            }
            my_panel_esd_recover(ctx);
        } else {
   
            // 读取正常,清空失败计数
            if (fail_cnt > 0) {
   
                dev_dbg(&ctx->dsi->dev, "ESD back to normal, clear fail counter\n");
                fail_cnt = 0;
            }
        }
    }
    dev_info(&ctx->dsi->dev, "ESD thread exit\n");
    return 0;
}
/**
 * my_panel_esd_start - 点亮屏后,启动ESD检测线程
 */
static int my_panel_esd_start(struct my_panel_data *ctx)
{
   
    if (ctx->esd_thread) {
   
        dev_warn(&ctx->dsi->dev, "ESD thread already running\n");
        return 0;
    }
    ctx->esd_thread_run = true;
    ctx->esd_thread = kthread_run(my_panel_esd_thread, ctx, "my_panel_esd");
    if (IS_ERR(ctx->esd_thread)) {
   
        dev_err(&ctx->dsi->dev, "Create esd kthread failed\n");
        ctx->esd_thread = NULL;
        return PTR_ERR(ctx->esd_thread);
    }
    dev_info(&ctx->dsi->dev, "ESD thread created, interval:%d ms\n", ctx->esd_poll_interval);
    return 0;
}
/**
 * my_panel_esd_stop - suspend休眠,停止ESD线程
 */
static void my_panel_esd_stop(struct my_panel_data *ctx)
{
   
    if (!ctx->esd_thread)
        return;
    ctx->esd_thread_run = false;
    kthread_stop(ctx->esd_thread);
    ctx->esd_thread = NULL;
    dev_info(&ctx->dsi->dev, "ESD thread stopped\n");
}
// panel prepare:屏幕上电点亮,启动ESD
static int my_panel_prepare(struct drm_panel *panel)
{
   
    struct my_panel_data *ctx = to_my_panel(panel);
    int ret;
    // 电源、复位、MIPI初始化时序
    ret = xxx_panel_power_on(ctx);
    if (ret) {
   
        dev_err(&ctx->dsi->dev, "panel power on fail\n");
        return ret;
    }
    if (ctx->esd_enable) {
   
        my_panel_esd_start(ctx);
    }
    return 0;
}
// panel unprepare:屏幕休眠下电,停止ESD线程
static int my_panel_unprepare(struct drm_panel *panel)
{
   
    struct my_panel_data *ctx = to_my_panel(panel);
    if (ctx->esd_enable) {
   
        my_panel_esd_stop(ctx);
    }
    // 下电时序
    xxx_panel_power_off(ctx);
    return 0;
}
// probe初始化ESD参数、互斥锁
static int my_panel_probe(struct mipi_dsi_device *dsi)
{
   
    struct my_panel_data *ctx;
    ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
    // drm_panel 基础初始化代码省略
    mutex_init(&ctx->esd_lock);
    // ESD参数,可放到DTS属性配置
    ctx->esd_enable = true;
    ctx->esd_poll_interval = 3000;    // 3s轮询一次
    ctx->esd_max_retry = 3;           // 最多连续3次自动复位
    ctx->check_reg = 0x04;
    ctx->expect_val = 0x12;
    ctx->panel_in_recovery = false;
    // 后续panel注册逻辑省略
    return 0;
}

code**说明:锁、竞态、日志分级**

1. 互斥锁与防重入保护

  • mutex esd_lock:保护drm_panel_prepare/unprepare整个上下电临界区;
  • panel_in_recovery标记:避免 ESD 线程短时间多次触发复位,防止重复进入恢复流程;
  • suspend/resume 调用 unprepare 时,停止 ESD 线程,休眠阶段不会触发 DSI 读写。

2. 失败计数器 & 最大重试限制

增加fail_cnt和esd_max_retry:如果静电持续存在,复位后读寄存器依旧异常,ESD 线程无限循环复位,系统反复闪屏。超过最大重试次数,停止自动恢复,避免死循环导致系统crash。

3. dmesg 日志分级

  • dev_dbg:调试信息,打开 DEBUG 宏才打印,量产默认关闭,防止 log 刷屏;
  • dev_info:正常事件,线程启停、复位完成;
  • dev_warn:异常告警,ESD 检测到异常;
  • dev_err:严重错误,创建线程失败、超过最大重试次数。

内核编译开启CONFIG_DYNAMIC_DEBUG可以动态开关 dev_dbg 日志,调试很方便。当然很多平台又自己客制化的debug log,可通过sys节点开关。

四、代码关键点拆解

1. kthread线程启停规则

  • kthread_run:创建并启动内核线程;
  • kthread_stop:发信号等待线程退出,必须等待线程退出,不能直接置空指针;
  • 增加esd_thread_run标志做循环条件,保证线程可以安全退出;
  • suspend休眠时一定要停止ESD线程。休眠状态MIPI Host会关闭,继续读寄存器会大量打印错误log,甚至系统panic。比如在AOD状态会close esd。

2. 锁

ESD复位会调用drm_panel_prepare/unprepare,和panel本身suspend/resume流程存在并发竞争。 使用mutex互斥锁,保证同一时间,屏幕上电/下电/ESD复位只能有一个流程执行。

3. 寄存器选择坑

不要选择会改变屏幕状态的寄存器;优先选择Panel ID寄存器、只读状态寄存器。

4. 轮询间隔怎么设置

常规产品:3~5秒轮询一次。 太短:频繁DSI读操作,增加MIPI带宽开销、功耗; 太长:ESD发生后很久才能检测到,用户长时间看到花屏黑屏。

5. 常见bug:ESD反复复位

现象:ESD触发复位,复位完成读寄存器依然异常,线程无限循环复位。

根因:硬件静电持续存在,或者上电时序本身有问题,复位后屏幕依然无法正常工作。 对策:增加最大重试次数,连续多次失败后停止自动复位,避免死循环。

如果还解决不了就需要找FAE和器件同事协助了。

五、调试手段

  1. dmesg看ESD打印:dmesg | grep esd
  2. 临时关闭ESD:DTS或者驱动中关闭esd_enable,对比复现静电现象
  3. 抓MIPI总线波形:DSI读写指令,确认读寄存器有没有真实发出去
  4. 模拟ESD:静电枪测试,验证是否可以自动恢复

六、总结

ESD Check不是硬件消除静电,是软件故障兜底恢复机制。

整个链路:内核kthread周期运行 > MIPI DCS读Panel寄存器 > 校验返回值 > 判断ESD异常 > 调用panel上下电时序复位屏幕。

项目开发时重点需要把控:线程启停时机、休眠关闭检测、并发竞态保护。

相关文章
|
3天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1220 3
|
26天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9656 32
|
13天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
7天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
11天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
837 1
|
4天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
516 1
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
8天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
552 0
|
9天前
|
存储 人工智能 自然语言处理
千问办公官网入口链接在哪?2026年最新QwenWork测评、千问办公怎么样?一文看懂
千问办公(QwenWork)是阿里云推出的AI智能办公平台,提供网页端和阿里云官网双入口。支持PPT/Word/Excel生成、多模态处理、网页全栈搭建、钉钉深度集成等6大核心能力。个人版免费可用,高级版198元/月;企业版198元/人/月。新用户注册即赠2000积分。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
1125 0