第一部分 MMC/SDIO 控制器

第一章 MMC/SDIO 控制器在 Platform Bus 中的位置

MMC/SDIO 控制器通常作为一个 Platform 设备存在,用于连接 eMMC 芯片、SD 卡或 SDIO 设备(如 WiFi 模块)。它通过 SD 总线 进行通信,支持 1-bit、4-bit 或 8-bit 模式,数据传输速率可达数百 MB/s。

Linux 中 MMC 子系统分为三层:

  1. MMC 核心层 (drivers/mmc/core/):管理 MMC/SD 卡状态、分区、块 I/O 请求。

  2. MMC 控制器驱动(即本篇文章重点):具体的 SoC 主机驱动(如 mmc-dwcmshc.c)。

  3. MMC 块设备层:将 MMC 虚拟为 /dev/mmcblk0 等块设备。


第二章 Linux 5.10 典型 MMC 控制器驱动 —— mmc-dwcmshc.c

DWC MMC(DesignWare Mobile Storage Host Controller)是目前广泛使用的 eMMC/SD 控制器 IP,用于 Rockchip、Allwinner、Intel 等平台。以下代码基于 Linux 5.10 drivers/mmc/host/mmc-dwcmshc.c,展示核心逻辑。

2.1 硬件关键概念

  • SDHCI 兼容寄存器:类似标准 SDHCI 寄存器布局(但有一些扩展)。

  • DMA 引擎:通过 ADMA2 或 SDMA 搬运数据。

  • 中断:传输完成、命令完成、错误(CRC 错误、超时、启动错误)。

  • 时钟:内部主时钟(clk_xin)与输出时钟(clk_sd)分离。

2.2 核心代码

// 基于 Linux 5.10 drivers/mmc/host/mmc-dwcmshc.c(简化版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sd.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
​
/**
 * @brief DWC MMC 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 MMC/SDIO Controller 硬件实例。
 */
struct dwcmshc_priv {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    int irq;                       /**< MMC 中断号 */
    struct clk *clk_xin;           /**< 主输入时钟 */
    struct clk *clk_sd;            /**< 输出时钟(用于卡) */
    struct mmc_host *mmc;          /**< MMC 主机抽象 */
    struct mmc_request *mrq;       /**< 当前请求 */
    struct mmc_command *cmd;       /**< 当前命令 */
    struct mmc_data *data;         /**< 当前数据段 */
    spinlock_t lock;               /**< 保护硬件的锁 */
    dma_addr_t adma_table_phys;    /**< ADMA 描述符表物理地址 */
    u32 *adma_table_virt;          /**< ADMA 描述符表虚拟地址 */
    unsigned int adma_desc_num;    /**< ADMA 描述符数量 */
};
​
/* 寄存器偏移量(基于 SDHCI 标准 + DWC 扩展) */
#define DWC_MSHC_CTRL         0x00
#define DWC_MSHC_PWREN        0x04
#define DWC_MSHC_CLKSRC       0x08
#define DWC_MSHC_TMOUT        0x0C
#define DWC_MSHC_CMD          0x10
#define DWC_MSHC_ARG          0x14
#define DWC_MSHC_TRANS        0x18
#define DWC_MSHC_DATA         0x1C
#define DWC_MSHC_INT_STAT     0x20
#define DWC_MSHC_INT_EN       0x24
#define DWC_MSHC_CTRL2        0x28
#define DWC_MSHC_CAP          0x2C
#define DWC_MSHC_BLKCNT       0x30
#define DWC_MSHC_ADMA_CTRL    0x34
#define DWC_MSHC_ADMA_LOW     0x38
#define DWC_MSHC_ADMA_HIGH    0x3C
​
/**
 * @brief 向 MMC 控制器发送命令。
 * 
 * @param host 指向 dwcmshc_priv 结构。
 * @param cmd 指向 mmc_command。
 */
static void dwcmshc_send_command(struct dwcmshc_priv *priv,
                                 struct mmc_command *cmd)
{
    u32 ctrl;
    u32 cmd_idx = cmd->opcode;
    u32 arg = cmd->arg;
​
    // 1. 写入命令参数
    writel(arg, priv->base + DWC_MSHC_ARG);
​
    // 2. 配置命令寄存器
    ctrl = (cmd_idx & 0x3F);
    ctrl |= (1 << 8);   // 启用命令(CMD_EN)
    if (cmd->flags & MMC_RSP_PRESENT) {
        ctrl |= (1 << 4);   // 期望响应
        if (cmd->flags & MMC_RSP_136)
            ctrl |= (1 << 5);   // 长响应
    }
    if (cmd->flags & MMC_RSP_CRC)
        ctrl |= (1 << 6);   // 启用 CRC 检查
    if (cmd->flags & MMC_CMD_ADTC) {
        // 带数据的命令(如 CMD17 读取多个块)
        ctrl |= (1 << 7);   // 数据传输
        // 根据数据方向设置传输方向
        if (cmd->data->flags & MMC_DATA_READ)
            ctrl |= (1 << 2);   // 读(控制位在 TRNS 寄存器中)
    }
​
    // 3. 写入命令寄存器以启动传输
    writel(ctrl, priv->base + DWC_MSHC_CMD);
}
​
/**
 * @brief 初始化 ADMA 描述符表。
 * 
 * @param priv 指向 dwcmshc_priv 结构。
 */
static int dwcmshc_adma_init(struct dwcmshc_priv *priv)
{
    int ret;
​
    // 1. 分配 ADMA 描述符表(对齐到 64 字节边界)
    priv->adma_table_virt = dma_alloc_coherent(priv->mmc->parent,
                                              sizeof(u32) * 512,
                                              &priv->adma_table_phys,
                                              GFP_KERNEL);
    if (!priv->adma_table_virt) {
        dev_err(priv->mmc->parent, "Failed to allocate ADMA table\n");
        return -ENOMEM;
    }
    priv->adma_desc_num = 512;
​
    // 2. 将 ADMA 表物理地址写入硬件
    writel(lower_32_bits(priv->adma_table_phys),
           priv->base + DWC_MSHC_ADMA_LOW);
    writel(upper_32_bits(priv->adma_table_phys),
           priv->base + DWC_MSHC_ADMA_HIGH);
​
    // 3. 启用 ADMA2 模式
    writel(0x02, priv->base + DWC_MSHC_ADMA_CTRL);
​
    return 0;
}
​
/**
 * @brief 设置数据传输的 ADMA 描述符(链式描述符)。
 * 
 * @param priv 指向 dwcmshc_priv 结构。
 * @param data 指向 mmc_data。
 */
static void dwcmshc_setup_adma_desc(struct dwcmshc_priv *priv,
                                    struct mmc_data *data)
{
    u32 *desc = priv->adma_table_virt;
    dma_addr_t dma_addr;
    u32 len;
    int sg_count, i;
​
    if (!data->sg_len) return;
​
    sg_count = data->sg_len;
​
    // 遍历 scatterlist,填充 ADMA 描述符
    for (i = 0; i < sg_count; i++) {
        dma_addr = sg_dma_address(&data->sg[i]);
        len = sg_dma_len(&data->sg[i]);
​
        // ADMA 描述符结构:32位地址 + 32位控制
        // 每个描述符 8 字节
        desc[i * 2] = dma_addr;
        desc[i * 2 + 1] = (len & 0xFFFF) | (0x02 << 16) | (0x01 << 24);
        // 02: 设置不指定数据方向,01: 有效标志
    }
    // 最后一个描述符设置 EOS(描述符结束)
    desc[(sg_count - 1) * 2 + 1] |= (1 << 26); // END 位
​
    // 确保描述符写入可见
    wmb(); // 写内存屏障
}
​
/**
 * @brief 处理数据传输(使用 ADMA)。
 * 
 * @param priv 指向 dwcmshc_priv 结构。
 * @param data 指向 mmc_data。
 */
static void dwcmshc_data_transfer(struct dwcmshc_priv *priv,
                                  struct mmc_data *data)
{
    u32 ctrl;
​
    // 1. 设置 ADMA 描述符
    dwcmshc_setup_adma_desc(priv, data);
​
    // 2. 设置传输控制寄存器
    ctrl = 0;
    if (data->flags & MMC_DATA_READ)
        ctrl |= (1 << 4); // 读方向
    else
        ctrl &= ~(1 << 4); // 写方向
​
    ctrl |= (data->blocks << 16); // 块数量
    ctrl |= (1 << 8); // 块大小(默认为 512 字节)
    ctrl |= (1 << 2); // 启用 ADMA
    writel(ctrl, priv->base + DWC_MSHC_TRANS);
}
​
/**
 * @brief MMC 请求处理函数(由 MMC 核心层调用)。
 * 
 * @param host 指向 mmc_host。
 * @param mrq 指向 mmc_request。
 */
static void dwcmshc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
    struct dwcmshc_priv *priv = mmc_priv(mmc);
    struct mmc_command *cmd = mrq->cmd;
    struct mmc_data *data = mrq->data;
    unsigned long flags;
​
    spin_lock_irqsave(&priv->lock, flags);
​
    priv->mrq = mrq;
    priv->cmd = cmd;
    priv->data = data;
​
    // 1. 如果是带数据的命令,先设置数据
    if (data) {
        dwcmshc_data_transfer(priv, data);
    }
​
    // 2. 发送命令
    dwcmshc_send_command(priv, cmd);
​
    // 3. 启用中断
    writel(0xFFFFFFFF, priv->base + DWC_MSHC_INT_EN);
​
    spin_unlock_irqrestore(&priv->lock, flags);
}
​
/**
 * @brief MMC 中断处理函数。
 * 
 * 处理命令完成、传输完成、错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dwcmshc_priv 结构。
 */
static irqreturn_t dwcmshc_irq_handler(int irq, void *dev_id)
{
    struct dwcmshc_priv *priv = dev_id;
    u32 int_stat, int_en;
    int handled = 0;
​
    // 1. 读取中断状态
    int_stat = readl(priv->base + DWC_MSHC_INT_STAT);
    int_en = readl(priv->base + DWC_MSHC_INT_EN);
    int_stat &= int_en; // 仅处理已使能的中断
​
    // 2. 清除中断标志(写 1 清除)
    writel(int_stat, priv->base + DWC_MSHC_INT_STAT);
​
    // 3. 命令完成中断
    if (int_stat & (1 << 0)) { // 命令完成
        if (priv->cmd) {
            priv->cmd->resp[0] = readl(priv->base + DWC_MSHC_DATA);
            if (priv->cmd->flags & MMC_RSP_136) {
                // 读取长响应
                priv->cmd->resp[1] = readl(priv->base + DWC_MSHC_DATA + 4);
                priv->cmd->resp[2] = readl(priv->base + DWC_MSHC_DATA + 8);
                priv->cmd->resp[3] = readl(priv->base + DWC_MSHC_DATA + 12);
            }
            priv->cmd = NULL;
        }
        handled = 1;
    }
​
    // 4. 数据传输完成中断
    if (int_stat & (1 << 1)) { // 传输完成
        if (priv->data) {
            // 检查 CRC 错误
            if (int_stat & (1 << 5)) {
                priv->data->error = -EILSEQ; // CRC 错误
            } else if (int_stat & (1 << 6)) {
                priv->data->error = -ETIMEDOUT; // 超时
            }
            priv->data = NULL;
        }
        handled = 1;
    }
​
    // 5. 错误中断(CRC、超时、启动错误等)
    if (int_stat & (1 << 7)) { // 启动错误
        dev_err(priv->mmc->parent, "MMC start error\n");
        handled = 1;
    }
​
    // 6. 完成整个请求
    if (priv->mrq && (int_stat & ((1 << 0) | (1 << 1)))) {
        mmc_request_done(priv->mmc, priv->mrq);
        priv->mrq = NULL;
    }
​
    return handled ? IRQ_HANDLED : IRQ_NONE;
}
​
/**
 * @brief 设置 MMC 时钟。
 * 
 * @param host 指向 mmc_host。
 * @param rate 目标时钟频率。
 */
static int dwcmshc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
    struct dwcmshc_priv *priv = mmc_priv(mmc);
    unsigned long clk_rate;
    u32 ctrl, div;
​
    // 1. 设置时钟
    if (ios->clock) {
        clk_rate = clk_get_rate(priv->clk_xin);
        div = clk_rate / ios->clock;
        if (div < 1) div = 1;
        if (div > 0xFF) div = 0xFF;
​
        ctrl = readl(priv->base + DWC_MSHC_CTRL);
        ctrl &= ~(0xFF << 8);
        ctrl |= (div << 8);
        writel(ctrl, priv->base + DWC_MSHC_CTRL);
​
        // 启用时钟
        ctrl |= (1 << 0);
        writel(ctrl, priv->base + DWC_MSHC_CTRL);
    } else {
        // 禁用时钟
        ctrl = readl(priv->base + DWC_MSHC_CTRL);
        ctrl &= ~(1 << 0);
        writel(ctrl, priv->base + DWC_MSHC_CTRL);
    }
​
    // 2. 设置总线宽度
    ctrl = readl(priv->base + DWC_MSHC_CTRL2);
    ctrl &= ~(0x03 << 4);
    switch (ios->bus_width) {
    case MMC_BUS_WIDTH_1:
        ctrl |= (0x00 << 4);
        break;
    case MMC_BUS_WIDTH_4:
        ctrl |= (0x01 << 4);
        break;
    case MMC_BUS_WIDTH_8:
        ctrl |= (0x02 << 4);
        break;
    }
    writel(ctrl, priv->base + DWC_MSHC_CTRL2);
​
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 MMC 控制器硬件,注册到 MMC 核心。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dwcmshc_probe(struct platform_device *pdev)
{
    struct dwcmshc_priv *priv;
    struct mmc_host *mmc;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配 MMC 主机
    mmc = mmc_alloc_host(sizeof(struct dwcmshc_priv), &pdev->dev);
    if (!mmc)
        return -ENOMEM;
    platform_set_drvdata(pdev, mmc);
​
    priv = mmc_priv(mmc);
    priv->mmc = mmc;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);
​
    // 3. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    priv->irq = irq;
​
    // 4. 获取时钟
    priv->clk_xin = devm_clk_get(&pdev->dev, "xin");
    if (IS_ERR(priv->clk_xin))
        return PTR_ERR(priv->clk_xin);
    priv->clk_sd = devm_clk_get(&pdev->dev, "sd");
    if (IS_ERR(priv->clk_sd))
        return PTR_ERR(priv->clk_sd);
    clk_prepare_enable(priv->clk_xin);
    clk_prepare_enable(priv->clk_sd);
​
    spin_lock_init(&priv->lock);
​
    // 5. 初始化 MMC 主机能力
    mmc->ops = &dwcmshc_ops;
    mmc->f_max = clk_get_rate(priv->clk_sd) / 2;
    mmc->f_min = mmc->f_max / 256;
    mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
    mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23;
    mmc->max_seg_size = PAGE_SIZE * 64;
    mmc->max_req_size = PAGE_SIZE * 64;
    mmc->max_blk_count = 65535;
    mmc->max_blk_size = 512;
​
    // 6. 初始化 ADMA
    ret = dwcmshc_adma_init(priv);
    if (ret) {
        dev_err(&pdev->dev, "ADMA init failed\n");
        return ret;
    }
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, priv->irq, dwcmshc_irq_handler,
                           IRQF_SHARED, "dwcmshc", priv);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 8. 注册 MMC 主机
    ret = mmc_add_host(mmc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add MMC host\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "DWC MMC controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, priv->irq);
    return 0;
}
​
/**
 * @brief 移除函数。
 *
 * @param pdev Platform 设备指针。
 */
static int dwcmshc_remove(struct platform_device *pdev)
{
    struct mmc_host *mmc = platform_get_drvdata(pdev);
    struct dwcmshc_priv *priv = mmc_priv(mmc);
​
    // 1. 移除 MMC 主机
    mmc_remove_host(mmc);
​
    // 2. 禁用时钟
    clk_disable_unprepare(priv->clk_xin);
    clk_disable_unprepare(priv->clk_sd);
​
    // 3. 释放 ADMA 表
    if (priv->adma_table_virt) {
        dma_free_coherent(mmc->parent, sizeof(u32) * 512,
                          priv->adma_table_virt, priv->adma_table_phys);
    }
​
    // 4. 释放 MMC 主机
    mmc_free_host(mmc);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dwcmshc_of_match[] = {
    { .compatible = "rockchip,rk3399-dwcmshc" },
    { .compatible = "snps,dwcmshc" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dwcmshc_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dwcmshc_driver = {
    .probe = dwcmshc_probe,
    .remove = dwcmshc_remove,
    .driver = {
        .name = "dwcmshc",
        .of_match_table = dwcmshc_of_match,
    },
};
module_platform_driver(dwcmshc_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DWC Mobile Storage Host Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 MMC/SDIO 调试核心难点

3.1 卡检测与初始化失败

现象:SD 卡插入后,内核没有任何反应,/dev/mmcblk0 不出现。

原因

  • 卡检测 GPIO 配置错误。

  • 卡未上电(vmmc 电源未启用)。

  • 时钟初始化失败。

调试方法

  1. 检查卡检测 GPIO

    cat /sys/kernel/debug/gpio | grep cd
  2. 查看电源控制

    cat /sys/kernel/debug/regulator/regulator_summary | grep mmc

    确认 vmmc 供电正常。

  3. 强制探测

    echo 1 > /sys/devices/platform/soc/*dwcmshc/mmc_host/mmc0/scan

3.2 数据传输 CRC 错误

现象:读写文件时出现 -EILSEQ 错误,或文件系统挂载后频繁错误。

原因

  • 信号质量差(走线过长、干扰)。

  • 不稳定的时钟。

  • 卡损坏。

调试方法

  1. 降低总线速度:在 DTS 中设置 max-frequency = <10000000>;(10MHz)。

  2. 测试不同位宽:强制 1-bit 模式:

    echo 1 > /sys/devices/platform/soc/*dwcmshc/mmc_host/mmc0/mmc0:0001/bus_width
  3. 查看 CRC 统计

    cat /sys/kernel/debug/mmc0/iotrace

3.3 时钟相位问题(Tuning 失败)

现象:高速模式(HS200/HS400)下无法启动,或者数据错误。

原因:时钟相位(Phase shift)设置不当,导致数据采样窗口偏移。

调试方法

  1. 强制 Tuning

    echo 1 > /sys/devices/platform/soc/*dwcmshc/mmc_host/mmc0/execute_tuning
  2. 使用 debugfs 查看 Tuning 结果

    cat /sys/kernel/debug/mmc0/tuning
  3. 查看控制器 Tuning 状态寄存器:使用 devmem2 读取 DWC_MSHC_TUNING 寄存器。

3.4 并发访问(多线程)导致死锁

现象:当多个线程同时访问 MMC 时,系统卡死,dmesg 显示 Timeout waiting for hardware

原因:中断处理程序中未能正确释放锁,或 DMA 完成未通知。

调试方法

  1. 使用 lockdep

    echo 1 > /proc/sys/kernel/lockdep/on
  2. 跟踪请求队列

    perf record -e mmc:* -a -- timeout 5
    perf script | grep -E "request|done"

第四章 结合性能调试场景示例

场景:嵌入式设备在写入大量文件时卡顿,iostat -x 1 显示 MMC 设备繁忙,但吞吐量远低于理论值。

分析流程

  1. 宏观层面(CPU 和 I/O 层):

    • top 看到 kworker/mmc 占用较高。

    • iostat -x 1 显示 mmcblk0svctm(服务时间)很长。

  2. 查看 MMC 状态(设备驱动层):

    cat /sys/kernel/debug/mmc0/ios

    发现当前使用 4-bit 模式,但频率仅 25MHz(低于 eMMC 支持的 200MHz)。

  3. 追踪 MMC 请求(MMC 核心层):

    trace-cmd record -e mmc:mmc_request -e mmc:mmc_request_done
    trace-cmd report | grep "timeout"

    发现大量请求在 mmc_wait_for_req 中超时。

  4. 根本原因:eMMC 切换到 HS200 模式失败,降级到标准 SD 模式,但没有正确配置 Tuning。

  5. 解决方案

    • 在 DTS 中设置 cap-mmc-hw-resetmmc-hs200-1_8v

    • 手动触发 Tuning:echo 1 > /sys/devices/platform/soc/*dwcmshc/mmc_host/mmc0/execute_tuning

    • 验证后,读写速度提升 10 倍。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 数据传输通过 ADMA2 搬运 ADMA 描述符对齐、地址映射
PINCTRL 用于总线宽度(1/4/8 位)切换 引脚功能正确,无竞争
PMIC 提供电源(vmmc、vqmmc) 电压稳定,上电序列
Clock 提供高速时钟源 频率匹配,相位调整

第二部分 Watchdog 控制器

第一章 Watchdog 控制器在 Platform Bus 中的位置

Watchdog 控制器虽然不起眼,但在工业嵌入式系统中至关重要。它独立于 CPU 运行,负责检测系统是否“死锁”,并在超时未收到“喂狗”(keep-alive)信号时,自动触发硬件复位,使系统恢复。

它通常挂载在 Platform Bus 上,驱动程序非常简单:核心是在 probe 阶段初始化定时器,并在用户空间(如 systemd 的 watchdog 服务)定时写入 /dev/watchdog 节点来重置计数器。

Linux 中 Watchdog 子系统很简单:

  1. Watchdog 核心层 (drivers/watchdog/watchdog_dev.c):管理字符设备 /dev/watchdog

  2. Watchdog 控制器驱动(即本篇文章重点):实现具体的硬件 watchdog(如 dw_wdt.c)。


第二章 Linux 5.10 典型 Watchdog 控制器驱动 —— dw_wdt.c

DesignWare Watchdog 是 ARM 平台最常见的 watchdog IP 核之一,广泛用于 Rockchip、Allwinner、Intel 等 SoC。

2.1 硬件关键寄存器

  • WDOG_CONTROL_REG_OFFSET:控制寄存器(使能、复位模式)。

  • WDOG_TIMEOUT_REG_OFFSET:超时值寄存器(计数器初值)。

  • WDOG_CURRENT_REG_OFFSET:当前计数器值(向下计数)。

  • WDOG_COUNTER_RESTART_REG_OFFSET:重置计数器(喂狗)寄存器。

2.2 核心代码

// 基于 Linux 5.10 drivers/watchdog/dw_wdt.c(精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
​
/**
 * @brief DesignWare Watchdog 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Watchdog Controller 硬件实例。
 */
struct dw_wdt {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    int irq;                       /**< Watchdog 中断号(如超时前产生中断) */
    struct clk *clk;               /**< 输入时钟 */
    struct watchdog_device wdd;    /**< 核心 watchdog 抽象 */
    bool nowayout;                 /**< 一旦启动,不可撤销 */
    spinlock_t lock;               /**< 保护硬件操作的锁 */
    struct reset_control *rst;     /**< 复位控制器(用于实际硬件复位) */
};
​
/* 寄存器偏移量 */
#define WDOG_CONTROL_REG_OFFSET        0x00
#define WDOG_TIMEOUT_REG_OFFSET        0x04
#define WDOG_CURRENT_REG_OFFSET        0x08
#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0C
#define WDOG_ISR_OFFSET                0x10
#define WDOG_IMR_OFFSET                0x14
​
/**
 * @brief 使能 Watchdog 硬件。
 * 
 * @param dw_wdt 指向 dw_wdt 结构。
 */
static void dw_wdt_enable(struct dw_wdt *dw_wdt)
{
    u32 val;
    unsigned long flags;
​
    spin_lock_irqsave(&dw_wdt->lock, flags);
    val = readl(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
    val |= (1 << 0);   // WDT_EN 位:启用看门狗
    val |= (1 << 2);   // WDT_RMOD 位:超时触发复位
    writel(val, dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
    spin_unlock_irqrestore(&dw_wdt->lock, flags);
}
​
/**
 * @brief 禁用 Watchdog 硬件。
 * 
 * @param dw_wdt 指向 dw_wdt 结构。
 */
static void dw_wdt_disable(struct dw_wdt *dw_wdt)
{
    u32 val;
    unsigned long flags;
​
    spin_lock_irqsave(&dw_wdt->lock, flags);
    val = readl(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
    val &= ~(1 << 0);  // 清除 WDT_EN 位
    writel(val, dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
    spin_unlock_irqrestore(&dw_wdt->lock, flags);
}
​
/**
 * @brief 喂狗(重置看门狗计数器)。
 * 
 * 写入任意值到 WDOG_COUNTER_RESTART_REG_OFFSET 即可复位计数器。
 *
 * @param dw_wdt 指向 dw_wdt 结构。
 */
static void dw_wdt_restart(struct dw_wdt *dw_wdt)
{
    unsigned long flags;
​
    spin_lock_irqsave(&dw_wdt->lock, flags);
    writel(0x1, dw_wdt->base + WDOG_COUNTER_RESTART_REG_OFFSET);
    spin_unlock_irqrestore(&dw_wdt->lock, flags);
}
​
/**
 * @brief 设置超时时间。
 * 
 * @param dw_wdt 指向 dw_wdt 结构。
 * @param timeout 超时时间(秒)。
 */
static void dw_wdt_set_timeout(struct dw_wdt *dw_wdt, unsigned int timeout)
{
    unsigned long clk_rate;
    u32 val;
​
    clk_rate = clk_get_rate(dw_wdt->clk);
    // 计算计数器的初始值:timeout * clk_rate
    val = timeout * clk_rate;
    // 限制在 32 位范围内
    if (val > 0xFFFFFFFF)
        val = 0xFFFFFFFF;
​
    writel(val, dw_wdt->base + WDOG_TIMEOUT_REG_OFFSET);
}
​
/**
 * @brief Watchdog 核心层回调函数:获取当前超时值。
 * 
 * @param wdd 指向 watchdog_device 结构。
 * @return 超时值(秒)。
 */
static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd)
{
    struct dw_wdt *dw_wdt = watchdog_get_drvdata(wdd);
    u32 cur = readl(dw_wdt->base + WDOG_CURRENT_REG_OFFSET);
    unsigned long clk_rate;
​
    clk_rate = clk_get_rate(dw_wdt->clk);
    if (clk_rate == 0)
        return 0;
​
    return cur / clk_rate;
}
​
/**
 * @brief Watchdog 核心层回调函数:启动。
 * 
 * @param wdd 指向 watchdog_device 结构。
 * @return 0 成功。
 */
static int dw_wdt_start(struct watchdog_device *wdd)
{
    struct dw_wdt *dw_wdt = watchdog_get_drvdata(wdd);
​
    // 1. 设置超时
    dw_wdt_set_timeout(dw_wdt, wdd->timeout);
    // 2. 使能硬件
    dw_wdt_enable(dw_wdt);
    // 3. 首次喂狗
    dw_wdt_restart(dw_wdt);
​
    return 0;
}
​
/**
 * @brief Watchdog 核心层回调函数:停止。
 * 
 * @param wdd 指向 watchdog_device 结构。
 * @return 0 成功。
 */
static int dw_wdt_stop(struct watchdog_device *wdd)
{
    struct dw_wdt *dw_wdt = watchdog_get_drvdata(wdd);
​
    dw_wdt_disable(dw_wdt);
    return 0;
}
​
/**
 * @brief Watchdog 核心层回调函数:喂狗。
 * 
 * @param wdd 指向 watchdog_device 结构。
 * @return 0 成功。
 */
static int dw_wdt_ping(struct watchdog_device *wdd)
{
    struct dw_wdt *dw_wdt = watchdog_get_drvdata(wdd);
​
    dw_wdt_restart(dw_wdt);
    return 0;
}
​
/**
 * @brief Watchdog 核心层回调函数:设置超时。
 * 
 * @param wdd 指向 watchdog_device 结构。
 * @param timeout 新超时时间。
 * @return 0 成功。
 */
static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
{
    struct dw_wdt *dw_wdt = watchdog_get_drvdata(wdd);
​
    wdd->timeout = timeout;
    // 如果设备正在运行,立即生效
    if (watchdog_active(wdd)) {
        dw_wdt_set_timeout(dw_wdt, timeout);
        dw_wdt_restart(dw_wdt);
    }
    return 0;
}
​
/**
 * @brief Watchdog 中断处理函数(可选)。
 * 
 * 如果硬件支持"超时前中断",可用于打印调试信息或进行软重启。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dw_wdt 结构。
 */
static irqreturn_t dw_wdt_irq_handler(int irq, void *dev_id)
{
    struct dw_wdt *dw_wdt = dev_id;
​
    // 1. 读取中断状态寄存器
    u32 isr = readl(dw_wdt->base + WDOG_ISR_OFFSET);
    if (isr & 1) {
        // WDOG 超时即将发生
        dev_crit(dw_wdt->wdd.parent, "Watchdog timeout imminent! System will reset.\n");
        // 清除中断(写 1 清零)
        writel(isr, dw_wdt->base + WDOG_ISR_OFFSET);
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Watchdog 硬件并注册到核心层。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dw_wdt_probe(struct platform_device *pdev)
{
    struct dw_wdt *dw_wdt;
    struct resource *res;
    struct watchdog_device *wdd;
    int ret, irq;
    unsigned long clk_rate;
​
    // 1. 分配私有数据结构
    dw_wdt = devm_kzalloc(&pdev->dev, sizeof(*dw_wdt), GFP_KERNEL);
    if (!dw_wdt)
        return -ENOMEM;
    platform_set_drvdata(pdev, dw_wdt);
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    dw_wdt->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dw_wdt->base))
        return PTR_ERR(dw_wdt->base);
​
    // 3. 获取时钟(用于计算超时值)
    dw_wdt->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(dw_wdt->clk))
        return PTR_ERR(dw_wdt->clk);
    clk_prepare_enable(dw_wdt->clk);
​
    // 4. 获取中断(并非所有 Watchdog 都有)
    irq = platform_get_irq(pdev, 0);
    if (irq > 0) {
        dw_wdt->irq = irq;
        ret = devm_request_irq(&pdev->dev, irq, dw_wdt_irq_handler,
                               IRQF_SHARED, "dw-wdt", dw_wdt);
        if (ret) {
            dev_err(&pdev->dev, "Failed to request IRQ\n");
            return ret;
        }
        // 启用中断(让硬件在超时前产生中断)
        writel(0x1, dw_wdt->base + WDOG_IMR_OFFSET);
    }
​
    // 5. 初始化 watchdog_device 结构
    wdd = &dw_wdt->wdd;
    wdd->info = &dw_wdt_info;
    wdd->ops = &dw_wdt_ops;
    wdd->min_timeout = 1;
    // 根据时钟计算出最大超时(假设 32 位计数器)
    clk_rate = clk_get_rate(dw_wdt->clk);
    wdd->max_timeout = 0xFFFFFFFF / clk_rate;
    wdd->timeout = min(wdd->max_timeout, 30U); // 默认 30 秒
    wdd->parent = &pdev->dev;
    wdd->bootstatus = (readl(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) & (1 << 4)) ?
                        WDIOF_CARDRESET : 0;
​
    watchdog_set_drvdata(wdd, dw_wdt);
    spin_lock_init(&dw_wdt->lock);
​
    // 6. 注册到 Watchdog 核心
    ret = watchdog_register_device(wdd);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register watchdog\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "DesignWare Watchdog registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, dw_wdt->irq);
    return 0;
}
​
/**
 * @brief 移除函数。
 *
 * @param pdev Platform 设备指针。
 */
static int dw_wdt_remove(struct platform_device *pdev)
{
    struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
​
    // 1. 注销
    watchdog_unregister_device(&dw_wdt->wdd);
​
    // 2. 禁用硬件(如果已经启动)
    if (watchdog_active(&dw_wdt->wdd))
        dw_wdt_disable(dw_wdt);
​
    // 3. 禁用时钟
    clk_disable_unprepare(dw_wdt->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dw_wdt_of_match[] = {
    { .compatible = "snps,dw-wdt" },
    { .compatible = "snps,dw-wdt-0" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dw_wdt_driver = {
    .probe = dw_wdt_probe,
    .remove = dw_wdt_remove,
    .driver = {
        .name = "dw-wdt",
        .of_match_table = dw_wdt_of_match,
    },
};
module_platform_driver(dw_wdt_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare Watchdog Driver");
MODULE_LICENSE("GPL v2");

第三章 Watchdog 调试核心难点

3.1 系统无故重启

现象:设备正常运行中突然重启,dmesg 无任何异常日志,只知道系统被复位了。

原因:Watchdog 没有按时被喂狗。可能因为:

  • 实时进程(高优先级)占用了 CPU,导致 systemd 的喂狗进程未被调度。

  • 喂狗线程被无限期阻塞(如长时间持有自旋锁)。

  • Watchdog 超时设置过短。

调试方法

  1. 确认复位原因

    # 检查 watchdog 状态
    cat /sys/class/watchdog/watchdog0/status
  2. 查看上次复位的原因(某些 SoC 有复位原因寄存器):

    devmem2 <base_addr>+0x08  # 读取复位状态寄存器
  3. 监控喂狗过程

    # 使用 strace 跟踪喂狗进程
    strace -p $(pgrep systemd-watchdog) -e trace=write
  4. 增加超时时间

    # 在设备树中设置更大的 timeout
    timeout-sec = <60>;

3.2 喂狗失败导致系统死循环

现象:系统复位后,启动过程中再次复位,导致无法启动。

原因:Bootloader 启动了 Watchdog,但内核启动过程中没有及时喂狗。

调试方法

  1. 禁用 Bootloader 的 Watchdog:在 U-Boot 配置中禁用 CONFIG_WATCHDOG

  2. 在内核启动早期喂狗:在内核启动参数中设置 watchdog.early_ping=1

  3. 临时关闭 Watchdog 驱动

    # 在 kernel cmdline 中禁用
    dw_wdt.start_timeout=0

3.3 用户空间喂狗进程被阻塞

现象systemd-watchdog 进程运行正常,但喂狗失败。

原因systemd-watchdog 通过 /dev/watchdog 字符设备进行 write 操作,如果驱动中的 ioctlwrite 被阻塞,喂狗将失败。

调试方法

  1. 查看 /dev/watchdog 的权限

    ls -l /dev/watchdog
  2. 手动测试喂狗

    # 手动写入任意字符到 /dev/watchdog 测试
    echo "1" > /dev/watchdog
  3. 查看 Watchdog 设备的打开计数

    cat /proc/devices | grep watchdog

第四章 结合性能调试场景示例

场景:远程部署的工业设备在低温环境下偶尔无故重启,无任何日志,重启间隔不固定(2小时~8小时)。

分析流程

  1. 宏观层面(图谱的 Hardware 层):

    • 检查硬件状态:可能存在时钟源的精度问题。

  2. 查看复位原因

    devmem2 <wdt_base>+0x00

    发现 WDT_EN 位始终为 1,且 WDOG_TIMEOUT 值没有变化,说明 Watchdog 一直在运行。

  3. 测量喂狗实际间隔

    # 编写脚本记录每次喂狗的时间戳
    while true; do date >> /tmp/watchdog.log; sleep 5; done
    # 用 tcpdump 或 ssh 拉取日志查看
  4. 根本原因

    • 发现某些时间段喂狗间隔超过 30 秒(设备树配置的 timeout-sec)。

    • 排查发现,在低温环境下,clk_get_rate(dw_wdt->clk) 返回的时钟频率变慢,导致计数器溢出时间延长,但喂狗周期是固定的。

    • 因此,计数器溢出后触发复位,而不是真正的喂狗周期。

  5. 解决方案

    • 使用 timeout-sec 稍大,并确保喂狗进程的优先级稳定。

    • 在驱动中实现更准确的时钟漂移补偿。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
Reset Controller 实际触发复位 确保复位信号正确传送到 SoC
Clock Controller 提供计数器时钟 时钟源稳定,温漂补偿
PMIC/Power Management 看门狗触发时切断电源 在断电前保留日志
System Timer/HRT 用户空间喂狗计时 系统定时器中断调度正常

第六章 总结

  1. 不要在生产环境中完全禁用:如果必须调试,可以暂时将超时设得很大,但不要完全关闭。

  2. 确保喂狗进程有足够的优先级:在 systemd 中使用 WatchdogSecRestartSec 配合。

  3. 观察底层复位原因:使用 devmem2 读取复位原因寄存器可以区分是由于 Watchdog 复位还是其他原因。

  4. 利用性能图谱中的工具:结合 perftrace-cmdkmemleak 等工具来定位喂狗失败的根本原因。

第三部分 RTC 控制器

第一章 RTC 控制器在 Platform Bus 中的位置

RTC 控制器是嵌入式系统中用于维持系统时间的关键外设。即使主电源断电,它依靠备用电池(通常是纽扣电池)继续保持时间计数。在 Linux 中,RTC 驱动通常挂载在 Platform Bus 上(某些 SoC 内部 RTC),或者在 I2C/SPI 总线上(外部 RTC 芯片)。

RTC 子系统的核心逻辑:

  1. RTC 核心层 (drivers/rtc/rtc-dev.c):提供 /dev/rtc0 字符设备接口,以及 sysfs 时间读写。

  2. RTC 控制器驱动:实现具体的硬件操作(如 rtc-pl031.c)。

  3. 用户空间:通过 hwclockdate 与 RTC 交互。


第二章 Linux 5.10 典型 RTC 控制器驱动 —— rtc-pl031.c

PL031 (PrimeCell RTC) 是 ARM 架构中最标准的 RTC IP 核,广泛用于各种 Cortex-A 系列 SoC(如 Versatile, VExpress, 某些高通芯片)。它挂载在 APB 总线上,映射为内存空间。以下代码基于 Linux 5.10 drivers/rtc/rtc-pl031.c,展示核心逻辑。

2.1 硬件关键寄存器

  • RTC_DR:数据寄存器(当前时间的秒数,从 Unix 纪元开始)。

  • RTC_MR:匹配寄存器(可设置闹钟时间)。

  • RTC_LR:加载寄存器(设置时间)。

  • RTC_CR:控制寄存器(使能、中断使能)。

  • RTC_IMSC:中断掩码设置寄存器。

  • RTC_RIS:原始中断状态寄存器。

  • RTC_MIS:屏蔽中断状态寄存器。

  • RTC_ICR:中断清除寄存器。

2.2 核心代码

// 基于 Linux 5.10 drivers/rtc/rtc-pl031.c (精简版)
#include <linux/io.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
​
/**
 * @brief PL031 RTC 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 RTC Controller 硬件实例。
 */
struct pl031_rtc {
    void __iomem *base;          /**< 映射后的寄存器基址 */
    int irq;                     /**< 闹钟中断号 */
    struct rtc_device *rtc;      /**< RTC 核心抽象 */
    spinlock_t lock;             /**< 保护硬件寄存器 */
    struct device *dev;          /**< 设备指针 */
};
​
/* 寄存器偏移量 (据 PL031 手册) */
#define RTC_DR          0x00    // 数据寄存器
#define RTC_MR          0x04    // 匹配寄存器
#define RTC_LR          0x08    // 加载寄存器
#define RTC_CR          0x0C    // 控制寄存器
#define RTC_IMSC        0x10    // 中断掩码
#define RTC_RIS         0x14    // 原始中断状态
#define RTC_MIS         0x18    // 屏蔽中断状态
#define RTC_ICR         0x1C    // 中断清除
​
/**
 * @brief 读取当前时间 (从 RTC_DR 中获取秒数)。
 * 
 * @param dev 指向 pl031_rtc 结构。
 * @param time 时间结构体,填充结果。
 * @return 0 成功。
 */
static int pl031_read_time(struct device *dev, struct rtc_time *tm)
{
    struct pl031_rtc *rtc = dev_get_drvdata(dev);
    u32 time_sec;
​
    // 1. 读取 RTC 数据寄存器 (从 Unix 纪元开始的秒数)
    time_sec = readl(rtc->base + RTC_DR);
​
    // 2. 将秒数转换为 rtc_time 结构
    rtc_time_to_tm(time_sec, tm);
​
    return 0;
}
​
/**
 * @brief 设置当前时间 (写入 RTC_LR 寄存器)。
 * 
 * @param dev 指向 pl031_rtc 结构。
 * @param tm 要设置的时间。
 * @return 0 成功。
 */
static int pl031_set_time(struct device *dev, struct rtc_time *tm)
{
    struct pl031_rtc *rtc = dev_get_drvdata(dev);
    u32 time_sec;
    unsigned long flags;
​
    // 1. 将 rtc_time 转换为秒数
    rtc_tm_to_time(tm, &time_sec);
​
    // 2. 写入加载寄存器 (PL031 会自动更新 DR)
    spin_lock_irqsave(&rtc->lock, flags);
    writel(time_sec, rtc->base + RTC_LR);
    spin_unlock_irqrestore(&rtc->lock, flags);
​
    return 0;
}
​
/**
 * @brief 读取闹钟时间。
 * 
 * @param dev 指向 pl031_rtc 结构。
 * @param tm 闹钟时间结构体。
 * @return 0 成功。
 */
static int pl031_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
    struct pl031_rtc *rtc = dev_get_drvdata(dev);
    u32 match_sec;
    unsigned long flags;
​
    // 1. 读取匹配寄存器 (闹钟触发时间)
    spin_lock_irqsave(&rtc->lock, flags);
    match_sec = readl(rtc->base + RTC_MR);
    spin_unlock_irqrestore(&rtc->lock, flags);
​
    // 2. 转换为 rtc_time
    rtc_time_to_tm(match_sec, &alarm->time);
​
    // 3. 检查闹钟是否使能 (从 IMSC 寄存器读取)
    alarm->enabled = (readl(rtc->base + RTC_IMSC) & (1 << 0)) ? 1 : 0;
​
    return 0;
}
​
/**
 * @brief 设置闹钟时间。
 * 
 * @param dev 指向 pl031_rtc 结构。
 * @param alarm 闹钟时间。
 * @return 0 成功。
 */
static int pl031_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
    struct pl031_rtc *rtc = dev_get_drvdata(dev);
    u32 match_sec;
    unsigned long flags;
​
    // 1. 转换时间到秒
    rtc_tm_to_time(&alarm->time, &match_sec);
​
    // 2. 写入匹配寄存器
    spin_lock_irqsave(&rtc->lock, flags);
    writel(match_sec, rtc->base + RTC_MR);
​
    // 3. 设置中断使能
    if (alarm->enabled)
        writel(1 << 0, rtc->base + RTC_IMSC); // 使能闹钟中断
    else
        writel(0, rtc->base + RTC_IMSC);
    spin_unlock_irqrestore(&rtc->lock, flags);
​
    return 0;
}
​
/**
 * @brief 闹钟中断处理函数。
 * 
 * @param irq 中断号。
 * @param dev_id 指向 pl031_rtc 结构。
 */
static irqreturn_t pl031_alarm_irq_handler(int irq, void *dev_id)
{
    struct pl031_rtc *rtc = dev_id;
    u32 mis;
​
    // 1. 检查中断状态 (屏蔽中断状态)
    mis = readl(rtc->base + RTC_MIS);
    if (!mis)
        return IRQ_NONE;
​
    // 2. 清除中断 (写 ICR)
    writel(mis, rtc->base + RTC_ICR);
​
    // 3. 通知 RTC 核心闹钟触发
    rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF);
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int pl031_probe(struct platform_device *pdev)
{
    struct pl031_rtc *rtc;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
    if (!rtc)
        return -ENOMEM;
    platform_set_drvdata(pdev, rtc);
    rtc->dev = &pdev->dev;
​
    // 2. 获取并映射 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;
    rtc->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(rtc->base))
        return PTR_ERR(rtc->base);
​
    // 3. 获取中断 (闹钟)
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    rtc->irq = irq;
​
    spin_lock_init(&rtc->lock);
​
    // 4. 注册 RTC 设备
    rtc->rtc = devm_rtc_allocate_device(&pdev->dev);
    if (IS_ERR(rtc->rtc))
        return PTR_ERR(rtc->rtc);
​
    rtc->rtc->ops = &pl031_rtc_ops;
    rtc->rtc->range_max = U32_MAX; // 32 位计数器,理论上可计数到 2106 年
    rtc->rtc->range_min = 0;
​
    // 5. 注册中断
    ret = devm_request_irq(&pdev->dev, rtc->irq, pl031_alarm_irq_handler,
                           0, "pl031-rtc", rtc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 6. 使能 RTC (控制寄存器使能)
    writel(1 << 0, rtc->base + RTC_CR); // RTC_EN 位
​
    ret = devm_rtc_register_device(rtc->rtc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register RTC\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "PL031 RTC registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, rtc->irq);
    return 0;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int pl031_remove(struct platform_device *pdev)
{
    struct pl031_rtc *rtc = platform_get_drvdata(pdev);
​
    // 禁用中断 (清除 IMSC)
    writel(0, rtc->base + RTC_IMSC);
    // 禁用 RTC (清除 CR 的 RTC_EN)
    writel(0, rtc->base + RTC_CR);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id pl031_of_match[] = {
    { .compatible = "arm,pl031" },
    { .compatible = "arm,primecell" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pl031_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver pl031_driver = {
    .probe = pl031_probe,
    .remove = pl031_remove,
    .driver = {
        .name = "pl031",
        .of_match_table = pl031_of_match,
    },
};
module_platform_driver(pl031_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ARM PL031 RTC Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 RTC 调试核心难点

3.1 系统时间回退或异常

现象:系统启动后,date 显示的时间是 1970-01-01 或其他不正确的时间。

原因

  • RTC 未初始化(新芯片需要写入基准时间)。

  • RTC 电池没电。

  • RTC 芯片本身损坏。

调试方法

  1. 查看 RTC 寄存器

    # 使用 devmem2 读取 RTC_DR 寄存器
    devmem2 <base_addr>+0x00

    如果返回值全为 0,说明 RTC 未初始化。

  2. 手动设置 RTC

    # 将系统时间写入 RTC
    hwclock -w
  3. 检查 RTC 电源:测量 RTC 引脚上的电压(通常 3V 纽扣电池)。

  4. 检查内核配置:确认 CONFIG_RTC_DRV_PL031=y

3.2 闹钟中断不触发或无限触发

现象:设置闹钟后,系统没有在规定时间唤醒,或者醒来后 dmesg 显示闹钟中断风暴。

原因

  • 中断配置错误。

  • 闹钟寄存器的值设置错误(过期)。

  • 中断未正确清除。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep pl031

    观察触发次数。

  2. 使用 trace-cmd 跟踪中断

    trace-cmd record -e rtc:* -e irq:*
    trace-cmd report | grep pl031
  3. 手动触发闹钟测试

    # 设置一个 10 秒后的闹钟
    echo +10 > /sys/class/rtc/rtc0/wakealarm

    然后 cat /sys/class/rtc/rtc0/wakealarm 查看是否已设置。

  4. 检查寄存器

    • 确认 RTC_MR 被正确设置。

    • 确认 RTC_IMSC 的位 0 为 1(闹钟中断使能)。

    • 确认 RTC_ICR 写后中断状态被清除。

3.3 时间漂移(精度问题)

现象:使用 RTC 维持时间,几天后系统时间偏离数分钟。

原因

  • RTC 晶振频率不准(热漂移或老化)。

  • 噪声耦合到时钟输入引脚。

调试方法

  1. 使用 hwclock --test 检查漂移

    hwclock --test -r
  2. 长时间监控 RTC

    while true; do date; hwclock -r; sleep 3600; done
  3. 调整晶振负载电容(硬件)。

  4. 使用 NTP 定期同步 RTC。


第四章 结合性能调试场景示例

场景:嵌入式设备在远程部署 6 个月后,RTC 时间每天慢 15 分钟,导致 NTP 同步不稳定。

分析流程

  1. 宏观层面( Hardware 层):

    • 确认 RTC 的晶振频率(通常 32.768kHz)。

    • 测量晶振实际频率(用示波器或频率计)。

  2. 查看 RTC 寄存器(RTC 控制器层):

    • devmem2 <base_addr>+0x00 看到计数器准确吗?

  3. 根本原因

    • 由于 PCB 设计或老化,晶振负载电容不匹配,导致振荡频率低于 32.768kHz。

    • 频率偏差:实际 32.767kHz,导致每天慢 25 秒。

  4. 解决方案

    • 硬件: 更换晶振或调整负载电容。

    • 软件:hwclock --systohc 前,先对 RTC_DR 进行软件修正(例如每 30 分钟将 RTC 读出的时间加 1 秒)。这是嵌入式系统的常用手法。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
PMC/Power Manager 使 RTC 在低功耗模式下保持计时 RTC 能否在睡眠模式下工作
GPIO 用于某些 RTC 的报警输出引脚 GPIO 中断配置
I2C/SPI 外部 RTC 芯片通过 I2C/SPI 连接 总线通信是否正常
System Timer 用于 RTC 内核时间同步 内核时间与 RTC 的同步周期

第四部分 PWM 控制器

第一章 PWM 控制器在 Platform Bus 中的位置

PWM 控制器通常作为独立的 Platform 设备存在,用于产生可编程的方波信号。它在嵌入式系统中应用极其广泛:

  • 屏幕背光调节:改变 LED 亮度。

  • 电机控制:伺服电机、直流电机调速。

  • 音频生成:通过 PWM 输出方波驱动蜂鸣器(Buzzer)。

  • 电压调节:配合外部电路实现高效的 DCDC 转换。

Linux 中 PWM 子系统分为两层:

  1. PWM 核心层 (drivers/pwm/core.c):提供 /sys/class/pwm/pwmchipX 和通用的 PWM API。

  2. PWM 控制器驱动(即本篇文章重点):具体的 SoC 主机驱动(如 pwm-rockchip.c)。


第二章 Linux 5.10 典型 PWM 控制器驱动 —— pwm-rockchip.c

Rockchip 系列 SoC 的 PWM 控制器是最典型的挂载在 APB 总线上的 Platform 设备之一。以下代码基于 Linux 5.10 drivers/pwm/pwm-rockchip.c,展示核心逻辑。

2.1 硬件关键概念

  • 寄存器:通常包含控制寄存器、周期寄存器、占空比寄存器、计数器寄存器。

  • 时钟:一个必须使能的 pclk(APB 总线时钟)用于寄存器访问,一个 timer_clk 用于生成 PWM 波形的实际频率。

  • 中断:PWM 通常不产生中断,但有些控制器支持在计数器溢出或达到特定点时触发中断。

  • 输出极性:可以配置为 High 有效或 Low 有效,这在调试信号极性不匹配时非常有用。

2.2 核心代码

// 基于 Linux 5.10 drivers/pwm/pwm-rockchip.c (简化版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/slab.h>
​
/**
 * @brief Rockchip PWM 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 PWM Controller 硬件实例。
 */
struct rockchip_pwm {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    struct clk *clk;               /**< APB 总线时钟 */
    struct clk *pclk;              /**< PWM 功能时钟 */
    struct pwm_chip chip;          /**< PWM 核心抽象 */
    struct device *dev;            /**< 设备指针 */
    int channel_count;             /**< 硬件包含的 PWM 通道数 */
};
​
/* 寄存器偏移量 (针对 Rockchip 常见版本) */
#define PWM_REG_CTRL        0x00
#define PWM_REG_PERIOD      0x04
#define PWM_REG_DUTY        0x08
#define PWM_REG_CNTR        0x0C
#define PWM_REG_CONFIG      0x10
​
/* 控制寄存器位定义 */
#define PWM_CTRL_ENABLE     (1 << 0)   /**< 全局使能 */
#define PWM_CTRL_CAP_MODE   (1 << 1)   /**< 捕获模式 */
#define PWM_CTRL_MODE_SHIFT (2)        /**< 模式选择 */
#define PWM_CTRL_MODE_MASK  (0x3 << 2) /**< 模式掩码 */
#define PWM_CTRL_POLARITY   (1 << 4)   /**< 极性选择 (1=高有效) */
#define PWM_CTRL_DEBUG      (1 << 5)   /**< 调试模式 */
​
/* 模式值 */
#define PWM_MODE_CONTINUOUS (0x0)      /**< 连续模式 */
#define PWM_MODE_ONESHOT    (0x1)      /**< 单次模式 */
#define PWM_MODE_CAPTURE    (0x2)      /**< 捕获模式 */
​
/**
 * @brief 将硬件配置应用于具体的 PWM 通道。
 *
 * @param pwm 指向 rockchip_pwm 结构。
 * @param state 需要应用的 pwm_state。
 */
static void rockchip_pwm_apply_hw(struct rockchip_pwm *pwm,
                                  struct pwm_state *state)
{
    u32 period = state->period;
    u32 duty = state->duty_cycle;
    u32 ctrl = readl_relaxed(pwm->base + PWM_REG_CTRL);
    
    // 1. 计算寄存器值:通常 period 和 duty 需要除以 `pwm->clk` 的速率
    // 这里简化处理,假定 period 和 duty 已经是适配硬件的 32 位计数值
    writel_relaxed(period, pwm->base + PWM_REG_PERIOD);
    writel_relaxed(duty, pwm->base + PWM_REG_DUTY);
​
    // 2. 设置极性
    if (state->polarity == PWM_POLARITY_NORMAL)
        ctrl &= ~PWM_CTRL_POLARITY;
    else
        ctrl |= PWM_CTRL_POLARITY;
​
    // 3. 设置模式 (连续模式)
    ctrl &= ~PWM_CTRL_MODE_MASK;
    ctrl |= (PWM_MODE_CONTINUOUS << PWM_CTRL_MODE_SHIFT);
​
    // 4. 使能或禁用 PWM
    if (state->enabled)
        ctrl |= PWM_CTRL_ENABLE;
    else
        ctrl &= ~PWM_CTRL_ENABLE;
​
    writel_relaxed(ctrl, pwm->base + PWM_REG_CTRL);
}
​
/**
 * @brief 核心 PWM 操作:获取当前 PWM 状态。
 *
 * @param chip 指向 pwm_chip 结构。
 * @param pwm 指向 pwm_device。
 * @param state 输出当前状态。
 * @return 0 成功。
 */
static int rockchip_pwm_get_state(struct pwm_chip *chip,
                                  struct pwm_device *pwm,
                                  struct pwm_state *state)
{
    struct rockchip_pwm *pc = to_rockchip_pwm(chip);
    u32 ctrl = readl_relaxed(pc->base + PWM_REG_CTRL);
    u32 period = readl_relaxed(pc->base + PWM_REG_PERIOD);
    u32 duty = readl_relaxed(pc->base + PWM_REG_DUTY);
​
    // 1. 获取使能状态
    state->enabled = !!(ctrl & PWM_CTRL_ENABLE);
​
    // 2. 获取极性
    if (ctrl & PWM_CTRL_POLARITY)
        state->polarity = PWM_POLARITY_INVERSED;
    else
        state->polarity = PWM_POLARITY_NORMAL;
​
    // 3. 获取周期和占空比 (需要从寄存器值反推,这里简化)
    state->period = period;
    state->duty_cycle = duty;
    
    return 0;
}
​
/**
 * @brief 核心 PWM 操作:应用新的 PWM 状态。
 *
 * @param chip 指向 pwm_chip。
 * @param pwm 指向 pwm_device。
 * @param state 指向新的 pwm_state。
 * @return 0 成功。
 */
static int rockchip_pwm_apply(struct pwm_chip *chip,
                              struct pwm_device *pwm,
                              struct pwm_state *state)
{
    struct rockchip_pwm *pc = to_rockchip_pwm(chip);
    struct pwm_state cur_state;
    int ret;
​
    // 1. 检查当前状态,决定是否重新配置
    rockchip_pwm_get_state(chip, pwm, &cur_state);
​
    if (state->period != cur_state.period ||
        state->duty_cycle != cur_state.duty_cycle ||
        state->polarity != cur_state.polarity) {
        
        // 必须先禁用 PWM 才能修改配置
        if (cur_state.enabled) {
            // 禁用硬件
            u32 ctrl = readl_relaxed(pc->base + PWM_REG_CTRL);
            ctrl &= ~PWM_CTRL_ENABLE;
            writel_relaxed(ctrl, pc->base + PWM_REG_CTRL);
            // 等待硬件完成关闭 (某些硬件需要时间)
            udelay(10);
        }
​
        // 应用新的硬件配置
        rockchip_pwm_apply_hw(pc, state);
​
        // 如果之前是 enabled 且新状态也是 enabled,重新启用
        if (state->enabled)
            rockchip_pwm_apply_hw(pc, state);
    } else if (state->enabled != cur_state.enabled) {
        // 仅修改使能状态,无需重新配置周期
        u32 ctrl = readl_relaxed(pc->base + PWM_REG_CTRL);
        if (state->enabled) {
            // 先设置极性,再使能
            if (state->polarity == PWM_POLARITY_NORMAL)
                ctrl &= ~PWM_CTRL_POLARITY;
            else
                ctrl |= PWM_CTRL_POLARITY;
            ctrl |= PWM_CTRL_ENABLE;
        } else {
            ctrl &= ~PWM_CTRL_ENABLE;
        }
        writel_relaxed(ctrl, pc->base + PWM_REG_CTRL);
    }
​
    return 0;
}
​
/* 核心 PWM 操作回调 */
static const struct pwm_ops rockchip_pwm_ops = {
    .apply = rockchip_pwm_apply,
    .get_state = rockchip_pwm_get_state,
};
​
/**
 * @brief Platform 探测函数。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_pwm_probe(struct platform_device *pdev)
{
    struct rockchip_pwm *pc;
    struct resource *res;
    int ret;
​
    // 1. 分配私有数据结构
    pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
    if (!pc)
        return -ENOMEM;
    platform_set_drvdata(pdev, pc);
    pc->dev = &pdev->dev;
​
    // 2. 获取并映射 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;
    pc->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(pc->base))
        return PTR_ERR(pc->base);
​
    // 3. 获取时钟
    pc->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(pc->clk))
        return PTR_ERR(pc->clk);
    
    pc->pclk = devm_clk_get_optional(&pdev->dev, "pclk");
    if (IS_ERR(pc->pclk))
        return PTR_ERR(pc->pclk);
​
    // 4. 使能时钟
    ret = clk_prepare_enable(pc->clk);
    if (ret) {
        dev_err(&pdev->dev, "Failed to enable clk\n");
        return ret;
    }
    if (pc->pclk) {
        ret = clk_prepare_enable(pc->pclk);
        if (ret) {
            dev_err(&pdev->dev, "Failed to enable pclk\n");
            clk_disable_unprepare(pc->clk);
            return ret;
        }
    }
​
    // 5. 获取通道数量 (从 DTS 或寄存器读取)
    pc->channel_count = 1; // 简化示例
​
    // 6. 初始化 PWM 芯片
    pc->chip.dev = &pdev->dev;
    pc->chip.ops = &rockchip_pwm_ops;
    pc->chip.npwm = pc->channel_count;
    pc->chip.base = -1;
​
    // 7. 注册到 PWM 核心
    ret = pwmchip_add(&pc->chip);
    if (ret < 0) {
        dev_err(&pdev->dev, "Failed to add PWM chip\n");
        goto err_pclk;
    }
​
    dev_info(&pdev->dev, "Rockchip PWM controller registered at 0x%llx, %d channels\n",
             (unsigned long long)res->start, pc->channel_count);
    return 0;
​
err_pclk:
    if (pc->pclk)
        clk_disable_unprepare(pc->pclk);
    clk_disable_unprepare(pc->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 *
 * @param pdev Platform 设备指针。
 */
static int rockchip_pwm_remove(struct platform_device *pdev)
{
    struct rockchip_pwm *pc = platform_get_drvdata(pdev);
​
    // 1. 删除 PWM 芯片
    pwmchip_remove(&pc->chip);
​
    // 2. 禁用时钟
    if (pc->pclk)
        clk_disable_unprepare(pc->pclk);
    clk_disable_unprepare(pc->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_pwm_of_match[] = {
    { .compatible = "rockchip,rk3399-pwm" },
    { .compatible = "rockchip,rk3288-pwm" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pwm_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_pwm_driver = {
    .probe = rockchip_pwm_probe,
    .remove = rockchip_pwm_remove,
    .driver = {
        .name = "rockchip-pwm",
        .of_match_table = rockchip_pwm_of_match,
    },
};
module_platform_driver(rockchip_pwm_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip PWM Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 PWM 调试核心难点

3.1 占空比/频率不准

现象/sys/class/pwm/ 下设置正确的周期和占空比,但实际测得的波形频率或占空比不符合预期。

原因

  • 输入时钟 clk 频率与代码中计算 period / duty 的公式不匹配。

  • 寄存器位宽(通常是 16 位或 32 位)与计数器溢出值不匹配。

  • 硬件分频器配置错误。

调试方法

  1. 检查时钟频率

    # 确认 PWM 功能时钟频率
    cat /sys/kernel/debug/clk/clk_summary | grep pwm
  2. 重新计算周期:假设输入时钟为 50 MHz,你需要 1 kHz 的频率,则计数器周期值应为 50,000。检查驱动中是否使用了正确公式。

  3. 直接读取寄存器

    devmem2 <base_addr>+0x04  # 读 PERIOD 寄存器
    devmem2 <base_addr>+0x08  # 读 DUTY 寄存器

3.2 PWM 输出始终无信号

现象:无论怎么配置,PWM 引脚始终没有输出波形。

原因

  • GPIO/PinMux 配置不正确。

  • PWM 时钟被禁用。

  • 配置未使能或极性错误。

调试方法

  1. 检查 PinMux

    cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep pwm
  2. 查看 GPIO 状态

    cat /sys/kernel/debug/gpio | grep pwm
  3. 确认配置生效

    # 手动触发一次配置
    echo 0 > /sys/class/pwm/pwmchip0/export
    echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
    echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
    echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
  4. 观察寄存器值:检查 PWM_REG_CTRL 的使能位是否置位。

3.3 极性翻转但示波器未变化

现象:用户空间设置 polarity = inversed,但波形极性未变化。

原因:硬件可能不支持双极性输出,或寄存器配置未生效。

调试方法

  1. 查看寄存器控制位devmem2 <base_addr>+0x00 检查 PWM_CTRL_POLARITY 位是否发生变化。

  2. 验证硬件能力:查阅 SoC 手册确认是否支持极性切换。

  3. 强制极性:在驱动中直接写寄存器,观察示波器变化,以排除用户空间的干扰。


第四章 结合性能调试场景示例

场景:系统背光调节异常。通过 PWM 调节屏幕背光时,亮度闪烁或调节无效。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • 运行 iostattop,确认 CPU 负载正常。

    • 检查 perf top 是否有大量的 pwm 相关系统调用。

  2. PWM 基本测试(PWM 控制器层):

    # 创建 PWM 节点并测试
    echo 0 > /sys/class/pwm/pwmchip0/export
    echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
    echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
    echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

    如果背光正常,说明驱动基本 OK。

  3. 检查调节逻辑(应用层):

    • 背光调节通常通过 sysfsioctl 实现。

    • 使用 strace 跟踪背光调节应用的 writeioctl 调用:

    strace -e trace=write -p $(pgrep backlight_daemon)
  4. 根本原因:背光调节应用程序在写入 duty_cycle 时,由于用户空间和驱动之间的通信问题,没有正确格式化字符串。例如:写入 1000 而不是正确的 500000

  5. 解决方案:修复应用程序的数值计算或格式化逻辑。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
GPIO/PinMux PWM 信号通过特定 GPIO 引脚输出 确保引脚未冲突,PinMux 配置正确
Clock Controller 提供 PWM 的输入时钟 时钟频率和稳定性,分频器计算
I2C/SPI 外部 PWM 驱动器或 LED 控制器通过总线访问 确保 I2C/SPI 通信正常
PMIC 为 PWM 电路提供电源 电压稳定

第五部分 Thermal 控制器(SoC 内部温度传感器)

第一章 Thermal 控制器在 Platform Bus 中的位置

Thermal 控制器(通常称为 TSADC,Temperature Sensor ADC)是挂载在 Platform Bus 上的 SoC 内部模块。它包含一个或多个温度传感器,用于监测芯片内部不同区域的温度(如 CPU 核心、GPU、DDR、PMIC 等),并能在温度超过阈值时触发中断或硬件降频。

在 Linux 中,Thermal 子系统非常成熟,核心架构是 Thermal Framework

  1. Thermal 核心层 (drivers/thermal/thermal_core.c):管理 热区 (Thermal Zone)冷却设备 (Cooling Device)热治理 (Thermal Governor)

  2. 温度传感器驱动(本篇文章重点):具体 SoC 的硬件驱动(如 rockchip_thermal.cqcom_tsens.c)。

  3. 冷却设备驱动:如 cpufreq_cooling.c(CPU 降频)、devfreq_cooling.c(GPU 降频)、fan_cooling.c(风扇)。

  4. 热治理策略:如 step_wise(台阶式)、fair_share(公平共享)、user_space(用户空间控制)。


第二章 Linux 5.10 典型 Thermal 控制器驱动 —— rockchip_thermal.c

Rockchip TSADC 是支持 4 个以上温度传感器通道的典型 Platform 设备,可检测 CPU、GPU、DDR 等区域温度,并支持硬件超温中断。

以下代码基于 Linux 5.10 drivers/thermal/rockchip_thermal.c,展示核心逻辑。

2.1 硬件关键概念

  • ADC 通道:每个传感器对应一个 ADC 通道(0~4),分别测量不同区域。

  • 寄存器:控制寄存器、数据寄存器(保存当前温度)、阈值寄存器(用于触发中断)。

  • 中断:可配置的高温警告、高温临界、低温警告等多个中断源。

  • 时钟:通常需要 tsadc_clk(ADC 采样时钟)和 pclk(总线时钟)。

2.2 核心代码

// 基于 Linux 5.10 drivers/thermal/rockchip_thermal.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/delay.h>
#include <linux/module.h>
​
#define ROCKCHIP_TSADC_MAX_SENSORS    5
#define ROCKCHIP_TSADC_POLL_DELAY     1000 // 毫秒
​
/**
 * @brief Rockchip Thermal 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Thermal Controller 硬件实例。
 */
struct rockchip_tsadc_priv {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    int irq;                       /**< 中断号 */
    struct clk *tsadc_clk;         /**< 采样时钟 */
    struct clk *pclk;              /**< 总线时钟 */
    struct device *dev;            /**< 设备指针 */
    struct thermal_zone_device *tzd[ROCKCHIP_TSADC_MAX_SENSORS]; /**< 热区设备 */
    int sensor_count;              /**< 实际传感器数量 */
    u32 *sensor_id_map;            /**< 传感器 ID 映射 */
};
​
/* 寄存器偏移量 (因 Rockchip 版本较多,此处为通用示例) */
#define TSADC_CON             0x00
#define TSADC_SAMPLE_RATE     0x04
#define TSADC_DATA_BASE       0x08  // 数据寄存器基址 (每个传感器 +4)
#define TSADC_INT_EN          0x1C
#define TSADC_INT_STAT        0x20
#define TSADC_INT_CFG         0x24
#define TSADC_THRESH_BASE     0x28  // 阈值寄存器基址
​
/* 控制寄存器位定义 */
#define TSADC_CON_SOC_EN      (1 << 0)  /**< 全局使能 */
#define TSADC_CON_SOC_PD      (1 << 1)  /**< 上电/掉电控制 */
#define TSADC_CON_SOC_PH      (1 << 2)  /**< 相位控制 */
​
/**
 * @brief 从硬件读取指定传感器的温度。
 * 
 * @param priv 指向 rockchip_tsadc_priv 结构。
 * @param sensor_idx 传感器索引 (0 ~ sensor_count-1)。
 * @return 温度值 (毫摄氏度,m℃),-1 表示错误。
 */
static int rockchip_thermal_read_temp(struct rockchip_tsadc_priv *priv,
                                      int sensor_idx)
{
    u32 data_reg, temp_raw, temp_mc;
    
    // 1. 读取传感器对应的数据寄存器
    data_reg = TSADC_DATA_BASE + sensor_idx * 4;
    temp_raw = readl_relaxed(priv->base + data_reg);
    
    // 2. 提取有效位 (假设 12 位 ADC,高 4 位为符号位)
    temp_raw &= 0xFFF;
    
    // 3. 转换为毫摄氏度 (每个 SoC 的转换公式不同)
    // 这里假设一个简单线性公式:Temp(°C) = (raw * 0.5) - 40
    temp_mc = (temp_raw * 500) - 40000;
    
    return temp_mc;
}
​
/**
 * @brief 为热区获取温度的通用回调函数。
 * 
 * @param tzd 指向 thermal_zone_device。
 * @param temp 指向输出温度 (毫摄氏度)。
 * @return 0 成功。
 */
static int rockchip_thermal_zone_get_temp(struct thermal_zone_device *tzd,
                                          int *temp)
{
    struct rockchip_tsadc_priv *priv = tzd->devdata;
    int sensor_idx = (int)tzd->id; // 假设热区 ID 对应传感器索引
    
    *temp = rockchip_thermal_read_temp(priv, sensor_idx);
    return 0;
}
​
/* 热区操作回调 */
static struct thermal_zone_device_ops rockchip_thermal_ops = {
    .get_temp = rockchip_thermal_zone_get_temp,
};
​
/**
 * @brief 中断处理函数。
 * 
 * 处理高温警告、高温临界、低温警告等中断。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_tsadc_priv 结构。
 */
static irqreturn_t rockchip_thermal_irq_handler(int irq, void *dev_id)
{
    struct rockchip_tsadc_priv *priv = dev_id;
    u32 int_stat, int_cfg;
​
    // 1. 读取中断状态
    int_stat = readl_relaxed(priv->base + TSADC_INT_STAT);
    int_cfg = readl_relaxed(priv->base + TSADC_INT_CFG);
    int_stat &= int_cfg;  // 仅处理使能的中断
​
    // 2. 检查中断类型
    if (int_stat & (1 << 0)) {
        // 高温临界 (Critical)
        dev_emerg(priv->dev, "Critical temperature exceeded! System will shutdown.\n");
        // 触发紧急关机或重置
        // ...
    }
    if (int_stat & (1 << 1)) {
        // 高温警告 (Warning)
        dev_warn(priv->dev, "High temperature warning detected!\n");
        // 通知热区
        thermal_zone_device_update(priv->tzd[0], THERMAL_EVENT_UNSPECIFIED);
    }
    if (int_stat & (1 << 2)) {
        // 低温警告
        dev_warn(priv->dev, "Low temperature warning detected!\n");
    }
    
    // 3. 清除中断 (写 1 清除)
    writel_relaxed(int_stat, priv->base + TSADC_INT_STAT);
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 初始化传感器硬件。
 * 
 * @param priv 指向 rockchip_tsadc_priv 结构。
 */
static void rockchip_thermal_hw_init(struct rockchip_tsadc_priv *priv)
{
    u32 con;
​
    // 1. 上电配置
    con = readl_relaxed(priv->base + TSADC_CON);
    con |= TSADC_CON_SOC_EN;
    con &= ~TSADC_CON_SOC_PD;
    writel_relaxed(con, priv->base + TSADC_CON);
​
    // 2. 配置采样率 (例如 1kHz)
    writel_relaxed(1000, priv->base + TSADC_SAMPLE_RATE);
​
    // 3. 设置阈值 (从设备树或硬件默认值读取)
    // 这里简单示例,设置高温警告阈值 85°C (需要转换为 ADC 原始值)
    // ...
    
    // 4. 使能中断
    writel_relaxed(0x7, priv->base + TSADC_INT_EN); // 使能所有中断
    writel_relaxed(0x7, priv->base + TSADC_INT_CFG);
}
​
/**
 * @brief 探测函数。
 * 
 * 初始化 Thermal 控制器硬件,注册热区。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_thermal_probe(struct platform_device *pdev)
{
    struct rockchip_tsadc_priv *priv;
    struct resource *res;
    struct thermal_zone_device *tz;
    int ret, i, irq;
​
    // 1. 分配私有数据
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    platform_set_drvdata(pdev, priv);
    priv->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);
​
    // 3. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    priv->irq = irq;
​
    // 4. 获取时钟
    priv->tsadc_clk = devm_clk_get(&pdev->dev, "tsadc_clk");
    if (IS_ERR(priv->tsadc_clk))
        return PTR_ERR(priv->tsadc_clk);
    priv->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(priv->pclk))
        return PTR_ERR(priv->pclk);
    
    clk_prepare_enable(priv->tsadc_clk);
    clk_prepare_enable(priv->pclk);
​
    // 5. 硬件初始化
    rockchip_thermal_hw_init(priv);
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, priv->irq, rockchip_thermal_irq_handler,
                           IRQF_SHARED, "rockchip-thermal", priv);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 7. 注册热区 (每个传感器一个)
    priv->sensor_count = 2; // 简化示例,假设有 2 个传感器
    for (i = 0; i < priv->sensor_count; i++) {
        tz = devm_thermal_zone_of_sensor_register(&pdev->dev,
                                                  i,
                                                  priv,
                                                  &rockchip_thermal_ops);
        if (IS_ERR(tz)) {
            dev_err(&pdev->dev, "Failed to register thermal zone %d\n", i);
            ret = PTR_ERR(tz);
            goto err_thermal;
        }
        priv->tzd[i] = tz;
    }
​
    dev_info(&pdev->dev, "Rockchip Thermal controller registered at 0x%llx, IRQ %d, %d sensors\n",
             (unsigned long long)res->start, priv->irq, priv->sensor_count);
    return 0;
​
err_thermal:
    // 取消注册热区 (框架会自动清理)
err_clk:
    clk_disable_unprepare(priv->tsadc_clk);
    clk_disable_unprepare(priv->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 *
 * @param pdev Platform 设备指针。
 */
static int rockchip_thermal_remove(struct platform_device *pdev)
{
    struct rockchip_tsadc_priv *priv = platform_get_drvdata(pdev);
​
    // 1. 禁用中断和硬件
    writel_relaxed(0, priv->base + TSADC_INT_EN);
    writel_relaxed(0, priv->base + TSADC_CON);
​
    // 2. 禁用时钟
    clk_disable_unprepare(priv->tsadc_clk);
    clk_disable_unprepare(priv->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_thermal_of_match[] = {
    { .compatible = "rockchip,rk3399-tsadc" },
    { .compatible = "rockchip,rk3288-tsadc" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_thermal_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_thermal_driver = {
    .probe = rockchip_thermal_probe,
    .remove = rockchip_thermal_remove,
    .driver = {
        .name = "rockchip-thermal",
        .of_match_table = rockchip_thermal_of_match,
    },
};
module_platform_driver(rockchip_thermal_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Thermal Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Thermal 调试核心难点

3.1 温度读数异常(过高或过低)

现象:通过 /sys/class/thermal/thermal_zoneX/temp 读取的温度远高于或低于实际室温。

原因

  • ADC 校准系数错误。

  • 硬件未校准(每个芯片出厂时需要写入校准数据,通常保存在 OTP 中)。

  • 温度传感器被部分物理损坏。

调试方法

  1. 读取原始寄存器值

    # 读取传感器的原始 ADC 值
    devmem2 <base_addr>+0x08
  2. 参考数据手册:将原始值手动代入该 SoC 专用的转换公式计算,对比实际温度。

  3. 检查传感器连接:某些芯片的传感器在引脚复用配置错误时可能接地或接 VCC,导致读数固定为 0 或最大值。

  4. 查阅校准 OTP

    # 读取校准数据 (通常保存在某个 NVM 或 OTP 中,通过特殊的命令或寄存器访问)
  5. 手动校准偏移:在驱动中通过添加软件偏移修正。

3.2 温度过高导致系统自动关机

现象:系统运行负载测试时,突然掉电或重启,dmesg 中出现 "Critical temperature exceeded!"。

原因:硬件高温临界阈值设置过低。部分 SoC 的临界温度为 90°C,在高负载环境下很容易超过。

调试方法

  1. 读取当前的温度

    # 查看温度
    for i in /sys/class/thermal/thermal_zone*/temp; do echo $i $(cat $i); done
  2. 查看阈值设置

    # 读取阈值寄存器
    devmem2 <base_addr>+0x28
  3. 临时调高临界值:在驱动中增加阈值偏移,或者通过 DTS 设置 temperature-high 属性。

  4. 优化散热:增加散热片或风扇。

  5. 动态调整降频策略:如果 CPU 降频策略滞后,可以修改 thermal_zone_device_update 的触发频率。

3.3 中断风暴(频繁触发高温警告)

现象:系统频繁打印 "High temperature warning detected!",CPU 占用率飙升。

原因:温度在高温阈值附近波动,导致中断频繁进入和退出。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep thermal
  2. 增加迟滞(Hysteresis):在驱动中为阈值增加迟滞区间,避免在阈值附近频繁触发中断。

  3. 降低采样率:如果 TSADC_SAMPLE_RATE 设置过高,导致检测过于灵敏。

  4. 使用 perf 监控中断

    perf record -e irq:* -a -- sleep 5
    perf script | grep thermal

3.4 热区冷却设备未生效

现象:温度超过阈值时,系统 CPU 频率未降低,导致持续过热。

原因:冷却设备(cpufreq_cooling)未正确绑定到热区。thermal_zone_device_of_sensor_register 未正确识别 DTS 中的 cooling-maps。

调试方法

  1. 查看热区绑定

    # 查看热区冷却设备
    cat /sys/class/thermal/thermal_zone*/cooling_device*
  2. 检查 DTS:确认 thermal-zones 节点下的 cooling-maps 配置正确。

  3. 手动触发冷却

    # 强制触发热区
    echo 90000 > /sys/class/thermal/thermal_zone0/emul_temp  # 模拟温度

    观察 CPU 频率是否下降:cpufreq-info

  4. 检查 cpufreq 驱动:确保 cpufreq 驱动已注册冷却设备。


第四章 结合性能调试场景示例

场景:嵌入式设备在播放 4K 视频时,突然因高温关机。但使用温度计测量外壳温度仅 40°C。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • top 显示 CPU 利用率 100%,GPU 满载。

    • perf top 显示 rockchip_thermal_irq_handler 没有调用,说明不是中断问题,而是直接触发了硬件临界保护。

  2. 热区状态(Thermal 核心层):

    cat /sys/class/thermal/thermal_zone*/temp

    发现 CPU 传感器读到 85°C,但 GPU 传感器读到 95°C(已经接近临界)。

  3. 检查阈值

    devmem2 <base_addr>+0x28  # 临界阈值寄存器

    发现值对应的温度是 90°C,GPU 超过并触发了硬件复位。

  4. 根本原因

    • GPU 散热设计不良,局部热点达到 95°C。

    • 系统没有为 GPU 配置独立的降频冷却设备。

    • 临界阈值设定太低(90°C),而在 4K 视频下 GPU 温度经常高于 90°C。

  5. 解决方案

    • 在 DTS 中为 GPU 热区绑定 devfreq_cooling 设备。

    • 调整热区治理策略为 step_wise 并增加降温台阶。

    • 适当提高临界阈值到 100°C(结合芯片的耐受温度)。

    • 改进 PCB 散热设计。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
CPU (CPUFreq) 高温时通过 cpufreq_cooling 降低 CPU 频率 降频策略响应是否及时
GPU (DevFreq) 高温时通过 devfreq_cooling 降低 GPU 频率 降频策略响应是否及时
PMIC 读取 PMIC 温度,以及热插拔控制 PMIC 与 SoC 之间通信正常
Clock Controller 提供 TSADC 采样时钟 时钟频率影响采样精度
Watchdog 高温临界时可能触发硬件复位 在复位前记录日志

第六部分 Ethernet MAC 控制器

第一章 Ethernet MAC 在 Platform Bus 中的位置

Ethernet MAC(Media Access Controller)是 SoC 内部负责网络数据收发的核心模块。它将数据封装成以太网帧,通过 MII/RMII/RGMII 接口连接到外部 PHY 芯片(物理层收发器),实现网络通信。

在 Linux 中,网络子系统非常庞大,但核心分为三层:

  1. 网络核心层 (net/core/):提供统一的 net_device 抽象和套接字接口。

  2. MAC 控制器驱动(本篇文章重点):具体的 SoC 以太网控制器驱动(如 stmmacdwmacgmac)。

  3. PHY 驱动:管理外部 PHY 芯片,通过 MDIO 总线配置。


第二章 Linux 5.10 典型 Ethernet MAC 驱动 —— dwmac-rockchip.c

Rockchip GMAC(Gigabit Ethernet MAC)是典型的基于 Synopsys DesignWare 内核的以太网控制器,广泛应用于 RK3399、RK3568 等平台。以下代码基于 Linux 5.10 drivers/net/ethernet/stmicro/stmmac/dwmac-rockchip.c,展示核心逻辑。

2.1 硬件关键概念

  • MAC 核心寄存器:控制发送/接收引擎、MAC 地址过滤、流控、VLAN 等。

  • DMA 引擎:支持双通道(发送 DMA 和接收 DMA),通过描述符链搬运数据。

  • 外部接口:支持 MII/RMII/RGMII/GMII 多种 PHY 接口模式。

  • 时钟:需要 stmmac_clk(MAC 主时钟)、pclk(总线时钟)、ptp_ref_clk(PTP 时钟)。

  • MDIO 接口:用于管理和配置外部 PHY 芯片。

2.2 核心代码

// 基于 Linux 5.10 drivers/net/ethernet/stmicro/stmmac/dwmac-rockchip.c
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
#include <linux/platform_device.h>
#include <linux/stmmac.h>
#include <linux/delay.h>
#include <linux/of_gpio.h>
#include <linux/of_net.h>
​
/**
 * @brief Rockchip GMAC 控制器的私有数据结构。
 * 
 * 对应图中 Platform Bus 上的 Ethernet MAC 控制器硬件实例。
 */
struct rockchip_dwmac {
    void __iomem *base;                /**< 映射后的寄存器基址 */
    struct device *dev;                /**< 设备指针 */
    struct clk *stmmac_clk;            /**< MAC 主时钟 */
    struct clk *pclk;                  /**< 总线时钟 */
    struct clk *ptp_ref_clk;           /**< PTP 时钟 */
    struct clk *clk_mac_speed;         /**< RGMII 速度时钟 */
    struct clk *clk_tx;                /**< 发送时钟 */
    struct clk *clk_rx;                /**< 接收时钟 */
    int phy_interface;                 /**< PHY 接口模式 (RGMII/RMII/MII) */
    u32 tx_delay;                      /**< 发送延迟 (用于 RGMII 相位调整) */
    u32 rx_delay;                      /**< 接收延迟 (用于 RGMII 相位调整) */
    struct device_node *phy_node;      /**< PHY 设备树节点 */
    struct stmmac_resources res;       /**< STMMAC 通用资源 */
};
​
/* 部分寄存器偏移量 (Rockchip 扩展) */
#define GMAC_GRP_CLK_CTRL           0x30
#define GMAC_GRP_RGMII_CTRL         0x34
#define GMAC_GRP_MDIO_CTRL          0x38
​
/**
 * @brief 配置 RGMII 相位。
 * 
 * 调整发送和接收时钟的相位,用于满足 RGMII 接口的时序要求。
 *
 * @param priv 指向 rockchip_dwmac 结构。
 */
static void rockchip_dwmac_set_rgmii_phase(struct rockchip_dwmac *priv)
{
    u32 ctrl = readl(priv->base + GMAC_GRP_RGMII_CTRL);
​
    // 清除旧的延迟配置
    ctrl &= ~0x7F;
​
    // 配置发送延迟 (tx_delay, 0~31)
    ctrl |= (priv->tx_delay & 0x1F) << 2;
​
    // 配置接收延迟 (rx_delay, 0~31)
    ctrl |= (priv->rx_delay & 0x1F) << 7;
​
    writel(ctrl, priv->base + GMAC_GRP_RGMII_CTRL);
}
​
/**
 * @brief 初始化 PHY 接口。
 * 
 * 根据 DTS 中的 phy-interface 配置 MAC 的接口模式。
 *
 * @param priv 指向 rockchip_dwmac 结构。
 * @return 0 成功。
 */
static int rockchip_dwmac_init_phy(struct rockchip_dwmac *priv)
{
    struct phy_device *phydev;
    struct net_device *ndev;
    int ret;
​
    ndev = alloc_etherdev_mqs(sizeof(struct stmmac_priv), 1, 1);
    if (!ndev)
        return -ENOMEM;
​
    // 1. 查找 PHY 设备 (通过 DTS 中的 phy-handle 或 phy-mode)
    phydev = of_phy_connect(ndev, priv->phy_node, NULL, 0, priv->phy_interface);
    if (!phydev) {
        dev_err(priv->dev, "Failed to connect PHY\n");
        free_netdev(ndev);
        return -ENODEV;
    }
​
    // 2. 配置 PHY 速度 (全双工/自适应)
    phy_set_max_speed(phydev, SPEED_1000);
    phy_start_aneg(phydev);
​
    priv->res.phy_node = priv->phy_node;
    return 0;
}
​
/**
 * @brief 设置网络设备接口参数。
 * 
 * @param dev 指向 net_device。
 * @param cmd 命令码。
 * @param data 参数指针。
 * @return 0 成功。
 */
static int rockchip_dwmac_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
    // 通常调用 STMMAC 核心的 ioctl
    // ... 
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 MAC 硬件,注册网络设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_dwmac_probe(struct platform_device *pdev)
{
    struct rockchip_dwmac *priv;
    struct stmmac_resources *res;
    struct resource *mem_res;
    int ret, irq;
​
    // 1. 分配私有数据
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    platform_set_drvdata(pdev, priv);
    priv->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!mem_res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    priv->base = devm_ioremap_resource(&pdev->dev, mem_res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);
    
    // 3. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
​
    // 4. 获取时钟
    priv->stmmac_clk = devm_clk_get(&pdev->dev, "stmmac_clk");
    if (IS_ERR(priv->stmmac_clk)) {
        dev_err(&pdev->dev, "Failed to get stmmac_clk\n");
        return PTR_ERR(priv->stmmac_clk);
    }
    priv->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(priv->pclk)) {
        dev_err(&pdev->dev, "Failed to get pclk\n");
        return PTR_ERR(priv->pclk);
    }
    priv->ptp_ref_clk = devm_clk_get_optional(&pdev->dev, "ptp_ref_clk");
    if (IS_ERR(priv->ptp_ref_clk))
        return PTR_ERR(priv->ptp_ref_clk);
​
    // 5. 使能时钟
    clk_prepare_enable(priv->stmmac_clk);
    clk_prepare_enable(priv->pclk);
    if (priv->ptp_ref_clk)
        clk_prepare_enable(priv->ptp_ref_clk);
​
    // 6. 读取 DTS 配置 (phy-interface, tx/rx delay)
    priv->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0);
    if (!priv->phy_node) {
        dev_err(&pdev->dev, "No phy-handle in DT\n");
        ret = -ENODEV;
        goto err_clk;
    }
​
    ret = of_get_phy_mode(pdev->dev.of_node, &priv->phy_interface);
    if (ret) {
        dev_err(&pdev->dev, "Failed to get phy-mode\n");
        goto err_clk;
    }
​
    if (priv->phy_interface == PHY_INTERFACE_MODE_RGMII ||
        priv->phy_interface == PHY_INTERFACE_MODE_RGMII_ID ||
        priv->phy_interface == PHY_INTERFACE_MODE_RGMII_RXID ||
        priv->phy_interface == PHY_INTERFACE_MODE_RGMII_TXID) {
        // 读取 RGMII 延迟配置
        of_property_read_u32(pdev->dev.of_node, "tx_delay", &priv->tx_delay);
        of_property_read_u32(pdev->dev.of_node, "rx_delay", &priv->rx_delay);
        // 配置 RGMII 相位
        rockchip_dwmac_set_rgmii_phase(priv);
    }
​
    // 7. 设置通用的 STMMAC 资源
    res = &priv->res;
    res->addr = priv->base;
    res->irq = irq;
    res->pdev = pdev;
    res->phy_node = priv->phy_node;
    res->phy_interface = priv->phy_interface;
​
    // 8. 调用 STMMAC 核心初始化网络设备
    ret = stmmac_dwmac_probe(pdev, res);
    if (ret) {
        dev_err(&pdev->dev, "STMMAC probe failed\n");
        goto err_clk;
    }
​
    dev_info(&pdev->dev, "Rockchip GMAC registered at 0x%llx, IRQ %d, %s\n",
             (unsigned long long)mem_res->start, irq,
             phy_modes(priv->phy_interface));
    return 0;
​
err_clk:
    clk_disable_unprepare(priv->stmmac_clk);
    clk_disable_unprepare(priv->pclk);
    if (priv->ptp_ref_clk)
        clk_disable_unprepare(priv->ptp_ref_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_dwmac_remove(struct platform_device *pdev)
{
    struct rockchip_dwmac *priv = platform_get_drvdata(pdev);
​
    // 1. 调用 STMMAC 核心移除
    stmmac_dwmac_remove(pdev);
​
    // 2. 禁用时钟
    clk_disable_unprepare(priv->stmmac_clk);
    clk_disable_unprepare(priv->pclk);
    if (priv->ptp_ref_clk)
        clk_disable_unprepare(priv->ptp_ref_clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_dwmac_of_match[] = {
    { .compatible = "rockchip,rk3399-gmac" },
    { .compatible = "rockchip,rk3568-gmac" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_dwmac_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_dwmac_driver = {
    .probe = rockchip_dwmac_probe,
    .remove = rockchip_dwmac_remove,
    .driver = {
        .name = "rockchip-dwmac",
        .of_match_table = rockchip_dwmac_of_match,
    },
};
module_platform_driver(rockchip_dwmac_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip GMAC Ethernet Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Ethernet MAC 调试核心难点

3.1 网络不通(无法连接)

现象ip link set eth0 up 后,ifconfig 显示 Link 状态 DOWN,无法获取 IP。

原因

  • PHY 芯片未被正确初始化和连接。

  • MAC 与 PHY 的接口模式(RGMII/RMII/MII)不匹配。

  • 时钟未使能或频率错误。

调试方法

  1. 检查 PHY 状态

    # 查看 MDIO 总线上的 PHY 设备
    mdio-tool dump <bus> <addr>
    # 或使用 ethtool
    ethtool eth0
  2. 检查 PHY 接口模式

    ethtool eth0 | grep "Speed"
  3. 验证时钟频率

    cat /sys/kernel/debug/clk/clk_summary | grep gmac
  4. 检查 Reset 引脚:确保 PHY 的复位引脚被正确拉高。

3.2 丢包率高,网络卡顿

现象iperf 测试带宽远低于理论值,ifconfig 显示大量的 rx droppedtx dropped

原因

  • 中断处理不及时。

  • 内核缓冲区被占满。

  • DMA 描述符不足。

  • RGMII 相位没有正确调整。

调试方法

  1. 查看网络统计

    netstat -i
    ethtool -S eth0
  2. 检查中断触发频率

    cat /proc/interrupts | grep gmac
  3. 使用 perf 监控网络中断

    perf record -e irq:* -p $(pgrep -f "stmmac")
    perf report
  4. 调整 RGMII 延迟:尝试通过 DTS 中的 tx_delay / rx_delay 调整,通常 RGMII 对时序敏感。

3.3 PTP 无法同步(时间戳丢失)

现象ptp4l 报告 No timestampbad timestamp

原因

  • PTP 时钟未使能或频率不正确。

  • 硬件时间戳功能未启用。

  • 中断处理中未读取时间戳寄存器。

调试方法

  1. 检查 PTP 内核支持

    # 检查硬件时间戳能力
    ethtool -T eth0
  2. 查看 PTP 中断

    cat /proc/interrupts | grep ptp
  3. 手动测试时间戳

    # 使用 phc2sys 查看时钟偏移
    phc2sys -s eth0 -c CLOCK_REALTIME -m

3.4 网络唤醒(Wake-on-LAN)失败

现象:系统进入睡眠模式后,无法通过魔术包唤醒。

原因

  • WOL 在 PHY 层未使能。

  • 电源管理未正确配置。

  • MAC 或 PHY 进入了低功耗状态后无法响应。

调试方法

  1. 检查 WOL 状态

    ethtool eth0 | grep Wake-on
  2. 使能 WOL

    ethtool -s eth0 wol g
  3. 检查电源管理

    # 确保 CONFIG_PM 配置正确
    cat /sys/kernel/debug/pm_genpd/pm_genpd_summary

第四章 结合性能调试场景示例

场景:使用 iperf3 测试千兆以太网吞吐量,从 PC 发送到开发板,只能达到 400Mbps,实际硬件能力为 1Gbps。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • top 显示 irq/gmac 中断处理占用 CPU 约 20%。

    • perf top -G 显示 stmmac_napi_polldma_sync_single_for_cpu 占用 CPU 超过 30%。

  2. 网络层查看(Ethernet MAC 层):

    ethtool -S eth0

    发现 rx_framework 很高,说明接收 DMA 处理效率低下。

  3. 跟踪 DMA 事件(图谱的 DMA 控制器层):

    trace-cmd record -e dma:* -e net:* -a -- timeout 5
    trace-cmd report | grep stmmac

    发现在 dma_sync_single_for_cpu 上的耗时几乎占一次 NAPI 轮询的一半。

  4. 根本原因

    • STMMAC 驱动默认使用 dma_map_single 来映射每个接收数据包。每次 NAPI 轮询中,调用 dma_sync_single_for_cpu 将 DMA 缓冲区刷新到 CPU 地址空间,这会触发昂贵的缓存无效化操作。

    • 流量越大,这种同步操作就越频繁。

  5. 解决方案

    • 切换使用 dma_alloc_coherent 分配 RX 缓冲区,避免每次轮询都进行同步。

    • 调整 NAPI 预算,使一次轮询处理更多数据包,减少每帧的中断开销。

    • ethtool -C eth0 rx-usecs 的聚合时间调大,允许 DMA 在中断前收集更多数据包。

    • 优化后,吞吐量可以提升到 900Mbps 以上。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 网络数据通过 DMA 传输到内存 DMA 描述符链完整,中断延迟低
PINCTRL 配置 RGMII/RMII 引脚 引脚复用不冲突,信号完整性
Clock 提供 MAC 和 PHY 时钟 时钟频率和相位调整(RGMII 时序)
PHY Driver 通过 MDIO 配置外 PHY 芯片 PHY 上电和初始化时序
PMIC 提供 PHY 电源 电压正常,无噪声

第七部分 DWC3 USB 控制器

第一章 DWC3 USB 控制器在 Platform Bus 中的位置

DWC3 (DesignWare USB 3.0) 是 Synopsys 公司提供的 USB 控制器 IP,广泛用于 SoC 中,支持 USB 2.0 和 USB 3.0 (SuperSpeed)。它挂载在 Platform Bus 上,作为USB主机控制器,管理物理端口、设备连接、枚举和传输。

在 Linux 中,USB 子系统非常成熟,核心层次分为:

  1. USB 核心层 (drivers/usb/core/):提供通用的 USB API、hcd、urb、总线抽象。

  2. DWC3 控制器驱动(本篇文章重点):基于 dwc3 的 SoC 特定驱动。

  3. PHY 驱动:负责 USB 物理层收发器(例如 usb3-phy-...)。

  4. DMA 引擎:DWC3 自带 DMA 控制器。


第二章 Linux 5.10 典型 DWC3 USB 控制器驱动 —— dwc3-rockchip.c

以下代码基于 Linux 5.10 drivers/usb/dwc3/dwc3-rockchip.c,展示核心逻辑。

2.1 硬件关键概念

  • 核心寄存器:配置 USB 速度、模式、电源管理。

  • 事件缓冲区 (Event Buffer):DWC3 通过中断通知主机事件。

  • DMA 描述符:支持链式传输,用于批量数据传输。

  • PHY 接口:需要获取 USB 物理层(hs_phy, ss_phy, hs_phy_utmi, ss_phy_pipe 等)。

2.2 核心代码

// 基于 Linux 5.10 drivers/usb/dwc3/dwc3-rockchip.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/usb/dwc3.h>
#include <linux/phy/phy.h>
​
/**
 * @brief Rockchip DWC3 USB 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 USB Controller 硬件实例。
 */
struct rockchip_dwc3 {
    struct device *dev;          /**< 设备指针 */
    struct clk *clk;             /**< USB 核心时钟 */
    struct clk *bus_clk;         /**< 总线时钟(用于注册访问) */
    struct phy *hs_phy;          /**< USB 2.0 PHY */
    struct phy *ss_phy;          /**< USB 3.0 PHY */
    struct usb_phy *usb2_phy;    /**< USB 2.0 PHY(另一类抽象) */
    struct dwc3 *dwc3;           /**< 核心 DWC3 实例指针 */
    u32 mode;                    /**< 模式:主机、设备、OTG */
    u32 *event_buffer;           /**< 事件缓冲区(用于中断处理) */
    int irq;                     /**< DWC3 中断号 */
};
​
/**
 * @brief 初始化 USB 控制器硬件。
 * 
 * @param dwc3_rockchip 指向 rockchip_dwc3 结构。
 * @return 0 成功。
 */
static int rockchip_dwc3_probe(struct platform_device *pdev)
{
    struct rockchip_dwc3 *dwc3_rockchip;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    dwc3_rockchip = devm_kzalloc(&pdev->dev, sizeof(*dwc3_rockchip), GFP_KERNEL);
    if (!dwc3_rockchip)
        return -ENOMEM;
    platform_set_drvdata(pdev, dwc3_rockchip);
    dwc3_rockchip->dev = &pdev->dev;
​
    // 2. 获取时钟
    dwc3_rockchip->clk = devm_clk_get(&pdev->dev, "ref_clk");
    if (IS_ERR(dwc3_rockchip->clk))
        return PTR_ERR(dwc3_rockchip->clk);
    dwc3_rockchip->bus_clk = devm_clk_get_optional(&pdev->dev, "bus_clk");
    if (IS_ERR(dwc3_rockchip->bus_clk))
        return PTR_ERR(dwc3_rockchip->bus_clk);
​
    clk_prepare_enable(dwc3_rockchip->clk);
    if (dwc3_rockchip->bus_clk)
        clk_prepare_enable(dwc3_rockchip->bus_clk);
​
    // 3. 获取 PHY (USB 2.0 和 USB 3.0 物理层)
    dwc3_rockchip->hs_phy = devm_phy_get(&pdev->dev, "usb2-phy");
    if (IS_ERR(dwc3_rockchip->hs_phy)) {
        dev_err(&pdev->dev, "Failed to get usb2-phy\n");
        ret = PTR_ERR(dwc3_rockchip->hs_phy);
        goto err_clk;
    }
    dwc3_rockchip->ss_phy = devm_phy_get_optional(&pdev->dev, "usb3-phy");
    if (IS_ERR(dwc3_rockchip->ss_phy)) {
        dev_err(&pdev->dev, "Failed to get usb3-phy\n");
        ret = PTR_ERR(dwc3_rockchip->ss_phy);
        goto err_clk;
    }
​
    // 4. 初始化 PHY
    phy_init(dwc3_rockchip->hs_phy);
    phy_power_on(dwc3_rockchip->hs_phy);
    if (dwc3_rockchip->ss_phy) {
        phy_init(dwc3_rockchip->ss_phy);
        phy_power_on(dwc3_rockchip->ss_phy);
    }
​
    // 5. 读取 DTS 中的模式配置 (dr_mode)
    dwc3_rockchip->mode = usb_get_dr_mode(&pdev->dev);
    if (dwc3_rockchip->mode < 0) {
        dev_err(&pdev->dev, "Failed to get dr_mode\n");
        ret = -EINVAL;
        goto err_phy;
    }
​
    // 6. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_phy;
    }
    dwc3_rockchip->irq = irq;
​
    // 7. 分配事件缓冲区 (中断处理时用于存放事件数据)
    dwc3_rockchip->event_buffer = devm_kzalloc(&pdev->dev, 
                                                DWC3_EVENT_BUFFER_SIZE, 
                                                GFP_KERNEL);
    if (!dwc3_rockchip->event_buffer) {
        ret = -ENOMEM;
        goto err_phy;
    }
​
    // 8. 分配核心 DWC3 结构
    dwc3_rockchip->dwc3 = devm_dwc3_allocate(&pdev->dev);
    if (IS_ERR(dwc3_rockchip->dwc3)) {
        ret = PTR_ERR(dwc3_rockchip->dwc3);
        goto err_phy;
    }
    
    // 9. 设置 DWC3 核心参数
    dwc3_rockchip->dwc3->dev = &pdev->dev;
    dwc3_rockchip->dwc3->mode = dwc3_rockchip->mode;
    dwc3_rockchip->dwc3->hs_phy = dwc3_rockchip->hs_phy;
    dwc3_rockchip->dwc3->ss_phy = dwc3_rockchip->ss_phy;
    dwc3_rockchip->dwc3->event_buffer = dwc3_rockchip->event_buffer;
​
    // 10. 注册 DWC3 核心
    ret = dwc3_core_init(dwc3_rockchip->dwc3);
    if (ret) {
        dev_err(&pdev->dev, "Failed to initialize DWC3 core\n");
        goto err_phy;
    }
​
    // 11. 注册 USB 控制器
    ret = dwc3_core_register(dwc3_rockchip->dwc3);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register DWC3 core\n");
        goto err_phy;
    }
​
    dev_info(&pdev->dev, "Rockchip DWC3 USB controller registered\n");
    return 0;
​
err_phy:
    if (dwc3_rockchip->ss_phy) {
        phy_power_off(dwc3_rockchip->ss_phy);
        phy_exit(dwc3_rockchip->ss_phy);
    }
    phy_power_off(dwc3_rockchip->hs_phy);
    phy_exit(dwc3_rockchip->hs_phy);
err_clk:
    if (dwc3_rockchip->bus_clk)
        clk_disable_unprepare(dwc3_rockchip->bus_clk);
    clk_disable_unprepare(dwc3_rockchip->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_dwc3_remove(struct platform_device *pdev)
{
    struct rockchip_dwc3 *dwc3_rockchip = platform_get_drvdata(pdev);
​
    // 1. 注销 USB 控制器
    dwc3_core_unregister(dwc3_rockchip->dwc3);
​
    // 2. 关闭 PHY
    if (dwc3_rockchip->ss_phy) {
        phy_power_off(dwc3_rockchip->ss_phy);
        phy_exit(dwc3_rockchip->ss_phy);
    }
    phy_power_off(dwc3_rockchip->hs_phy);
    phy_exit(dwc3_rockchip->hs_phy);
​
    // 3. 禁用时钟
    if (dwc3_rockchip->bus_clk)
        clk_disable_unprepare(dwc3_rockchip->bus_clk);
    clk_disable_unprepare(dwc3_rockchip->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_dwc3_of_match[] = {
    { .compatible = "rockchip,rk3399-dwc3" },
    { .compatible = "rockchip,rk3568-dwc3" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_dwc3_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_dwc3_driver = {
    .probe = rockchip_dwc3_probe,
    .remove = rockchip_dwc3_remove,
    .driver = {
        .name = "rockchip-dwc3",
        .of_match_table = rockchip_dwc3_of_match,
    },
};
module_platform_driver(rockchip_dwc3_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip DWC3 USB Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 USB 控制器调试核心难点

3.1 USB 设备无法识别

现象:插入 USB 设备,lsusb 显示无设备连接,内核无 usb 1-1: new high-speed USB device 日志。

原因

  • PHY 供电或时钟未使能。

  • 设备树中 dr_mode 错误(应设为 hostperipheral)。

  • VBUS 电源未开启。

调试方法

  1. 检查 USB 核心状态

    cat /sys/kernel/debug/usb/devices
  2. 检查 PHY 状态

    # 查看 PHY 设备
    ls /sys/class/phy/phy*
  3. 检查 VBUS 电源

    # 手动启用 VBUS 电源
    echo 1 > /sys/devices/platform/soc/*usb-vbus/gpio/value
  4. 查看中断状态

    cat /proc/interrupts | grep dwc3
  5. 检查 dmesg 日志

    dmesg | grep dwc3
    dmesg | grep usb

3.2 USB 3.0 降级为 2.0

现象:连接 USB 3.0 设备,速度显示为 480Mbps 而非 5Gbps。

原因

  • 外部 USB 3.0 PHY 初始化失败。

  • 信号完整性差,无法建立超高速链路。

  • 内核配置中未启用 USB 3.0 支持。

调试方法

  1. 检查 USB 速度

    lsusb -t
  2. 查看 PHY 注册

    dmesg | grep usb3-phy
  3. 检查 USB 3.0 内核配置

    zcat /proc/config.gz | grep CONFIG_USB_XHCI_HCD
  4. dwc3 调试节点中查看

    cat /sys/kernel/debug/dwc3/dwc3.0/regdump

3.3 通过 USB 传输数据丢包

现象dd if=/dev/sda of=/dev/null bs=1M 出现大量 I/O errors

原因

  • DMA 描述符溢出。

  • USB 总线上的带宽不足。

  • 中断处理被高优先级任务阻塞。

调试方法

  1. 查看 USB 统计

    cat /sys/kernel/debug/usb/ehci/0000:00:14.0/registers
  2. 使用 perf 监控中断处理延迟

    perf record -e irq:irq_handler_exit -a -- timeout 10
    perf script | grep dwc3
  3. 检查 DMA 描述符池

    cat /proc/dwc3/dwc3.0/descriptors

第四章 结合性能调试场景示例

场景:通过 USB 3.0 连接 U 盘拷贝大量文件时,速度仅在 10MB/s 左右,远低于理论值 400MB/s。

分析流程

  1. 宏观层面(CPU 层):

    • iostat -x 1 显示磁盘设备繁忙,但 %util 很高。

    • perf top -G 显示 dwc3_complete_trbdwc3_ep_dequeue 占用 CPU 较多。

  2. USB 层(USB 控制器层):

    cat /sys/kernel/debug/usb/devices

    显示处于 SuperSpeed 模式,说明 PHY 工作正常。

  3. 检查 DMA 配置(DMA 控制器层):

    trace-cmd record -e dma:* -e usb:* -a -- timeout 5
    trace-cmd report | grep dwc3

    发现 dma_sync_single_for_cpu 被频繁调用,导致大量缓存刷新。

  4. 根本原因

    • DWC3 默认使用 dma_map_single 映射每个 URB,对每个 TRB 都会进行 dma_sync_single_for_cpu

    • 对于批量传输(bulk transfer),频繁同步操作严重影响性能。

    • 同时发现中断频率很高,导致 CPU 忙于 ISR。

  5. 解决方案

    • dwc3_ep_dequeue 中优化同步逻辑,避免不必要的 dma_sync_single_for_cpu

    • 增加 dwc3 的 interrupt coalescing 设置,减少中断触发频率。

    • 使用 dwc3-queue-buffer-size 增加每 URB 的缓冲区大小。

    • 验证后,拷贝速度提升到 300MB/s 以上。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
PHY 物理层收发器 USB 2.0/3.0 PHY 正确初始化和上电
DMA 数据传输搬运 DMA 描述符链和中断延迟
GPIO VBUS 电源控制 VBUS 使能,电压检测
Pinctrl 控制 USB 引脚复用 确保引脚不被其他外设占用
PMIC 提供 USB 电源和电池充电检测 电源稳定,充电检测功能正确

第八部分 PCIe 控制器

第一章 PCIe 控制器在 Platform Bus 中的位置

PCIe (Peripheral Component Interconnect Express) 是一种高速串行总线,广泛用于连接高性能外设(如 WiFi 模块、SSD、GPU 等)。在嵌入式 SoC(如 Rockchip、Qualcomm、NXP 等)中,PCIe 控制器通常作为 Platform 设备挂载在内部总线上,并通过外部 PHY 与 PCIe 设备通信。

Linux 中 PCIe 子系统的核心层次:

  1. PCI 核心层 (drivers/pci/):管理 PCI 总线、设备、配置空间、电源管理、中断(MSI/MSI-X)。

  2. PCIe 主机控制器驱动(本篇文章重点):具体的 SoC PCIe 控制器驱动(如 pcie-rockchip.cpcie-designware.c)。

  3. PCIe PHY 驱动:负责物理层信号调理和链路训练。

  4. PCIe 设备驱动:具体 PCIe 设备(如 NVMe SSD、WiFi 卡)的驱动。


第二章 Linux 5.10 典型 PCIe 控制器驱动 —— pcie-rockchip.c

Rockchip PCIe 控制器基于 DesignWare PCIe 核心 IP,支持 PCIe 2.0/3.0,通常与外部 PHY 配合工作。以下代码基于 Linux 5.10 drivers/pci/controller/dwc/pcie-rockchip.c,展示核心逻辑。

2.1 硬件关键概念

  • DWC PCIe 核心:处理 PCIe 协议层、配置空间访问、链路管理。

  • 应用程序层 (App Layer):Rockchip 特有的寄存器扩展,用于控制链路状态、复位、中断。

  • PHY 接口:通过 PIPE 接口与外部 PCIe PHY 芯片连接。

  • DMA 引擎:支持 TLP 数据传输。

  • 中断:支持 MSI/MSI-X 和常规中断。

2.2 核心代码

// 基于 Linux 5.10 drivers/pci/controller/dwc/pcie-rockchip.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/reset.h>
#include <linux/delay.h>
#include <linux/of.h>
​
/**
 * @brief Rockchip PCIe 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 PCIe Controller 硬件实例。
 */
struct rockchip_pcie {
    struct dw_pcie pci;           /**< DesignWare PCIe 核心结构 */
    void __iomem *app_base;       /**< 应用层寄存器基址 */
    int irq;                      /**< PCIe 中断号 */
    struct clk *aclk;             /**< 总线时钟 (AXI) */
    struct clk *aclk_perf;        /**< 性能时钟 */
    struct clk *hclk;             /**< 主机时钟 */
    struct clk *pmclk;            /**< 电源管理时钟 */
    struct phy *phy;              /**< PCIe PHY 设备 */
    struct reset_control *rst;    /**< 复位控制器 */
    struct device *dev;           /**< 设备指针 */
    struct pci_host_bridge *bridge; /**< PCI 主机桥 */
    u32 link_gen;                 /**< 链路速度 (Gen1/Gen2/Gen3) */
    u32 lanes;                    /**< 链路通道数 (1/2/4) */
};
​
/* Rockchip 应用层寄存器偏移 */
#define PCIE_APP_CTRL             0x00
#define PCIE_APP_CFG              0x04
#define PCIE_APP_INT_STAT         0x08
#define PCIE_APP_INT_EN           0x0C
#define PCIE_APP_LINK_STAT        0x10
#define PCIE_APP_AXI_CTRL         0x14
​
/**
 * @brief PCIe 链路状态读取函数。
 * 
 * @param pci 指向 rockchip_pcie 结构。
 * @return 链路状态 (0=down, 1=up)
 */
static int rockchip_pcie_link_up(struct dw_pcie *pci)
{
    struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
    u32 stat;
​
    // 读取应用层链路状态寄存器
    stat = readl(rockchip->app_base + PCIE_APP_LINK_STAT);
    // 检查链路状态位 (假设 bit 0 为链路状态)
    return !!(stat & 0x1);
}
​
/**
 * @brief 开始 PCIe 链路训练。
 * 
 * @param pci 指向 dw_pcie 结构。
 */
static void rockchip_pcie_start_link(struct dw_pcie *pci)
{
    struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
    u32 ctrl;
​
    // 1. 启用核心 PLL 和 PHY
    phy_init(rockchip->phy);
    phy_power_on(rockchip->phy);
​
    // 2. 等待 PHY 稳定
    msleep(100);
​
    // 3. 配置应用层寄存器,启动链路训练
    ctrl = readl(rockchip->app_base + PCIE_APP_CTRL);
    ctrl |= (1 << 0);  // 启用链路训练
    writel(ctrl, rockchip->app_base + PCIE_APP_CTRL);
​
    // 4. 等待链路建立 (最多 3 秒)
    int i;
    for (i = 0; i < 30; i++) {
        if (rockchip_pcie_link_up(pci))
            break;
        msleep(100);
    }
    
    if (!rockchip_pcie_link_up(pci))
        dev_err(rockchip->dev, "PCIe link training timeout\n");
}
​
/**
 * @brief 停止 PCIe 链路。
 * 
 * @param pci 指向 dw_pcie 结构。
 */
static void rockchip_pcie_stop_link(struct dw_pcie *pci)
{
    struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
​
    phy_power_off(rockchip->phy);
    phy_exit(rockchip->phy);
}
​
/**
 * @brief 初始化 PCIe 控制器硬件。
 * 
 * @param rockchip 指向 rockchip_pcie 结构。
 */
static int rockchip_pcie_hw_init(struct rockchip_pcie *rockchip)
{
    // 1. 启用时钟
    clk_prepare_enable(rockchip->aclk);
    clk_prepare_enable(rockchip->aclk_perf);
    clk_prepare_enable(rockchip->hclk);
    clk_prepare_enable(rockchip->pmclk);
​
    // 2. 复位 PCIe 控制器
    reset_control_assert(rockchip->rst);
    udelay(10);
    reset_control_deassert(rockchip->rst);
    msleep(100);
​
    // 3. 配置链路速度 (Gen2/Gen3)
    // 在 DWC 核心配置寄存器中设置
    dw_pcie_setup_rc(&rockchip->pci);
​
    // 4. 初始化 PHY
    phy_init(rockchip->phy);
    phy_power_on(rockchip->phy);
    
    // 5. 启动链路训练
    rockchip_pcie_start_link(&rockchip->pci);
    
    return 0;
}
​
/**
 * @brief PCIe 中断处理函数。
 * 
 * 处理链路错误、PME (电源管理事件)、MSI 等。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_pcie 结构。
 */
static irqreturn_t rockchip_pcie_irq_handler(int irq, void *dev_id)
{
    struct rockchip_pcie *rockchip = dev_id;
    u32 int_stat, int_en;
​
    // 1. 读取中断状态
    int_stat = readl(rockchip->app_base + PCIE_APP_INT_STAT);
    int_en = readl(rockchip->app_base + PCIE_APP_INT_EN);
    int_stat &= int_en;
​
    // 2. 处理链路错误 (Link error)
    if (int_stat & (1 << 0)) {
        dev_err(rockchip->dev, "PCIe link error detected\n");
        // 清除中断
        writel(1 << 0, rockchip->app_base + PCIE_APP_INT_STAT);
        // 可能需要重新训练链路
        rockchip_pcie_start_link(&rockchip->pci);
    }
​
    // 3. 处理电源管理事件 (PME)
    if (int_stat & (1 << 1)) {
        dev_info(rockchip->dev, "PCIe PME event received\n");
        // 唤醒可能处于睡眠状态的设备
        writel(1 << 1, rockchip->app_base + PCIE_APP_INT_STAT);
    }
​
    // 4. 处理其他中断 (MSI 等)
    // DWC 核心会处理 MSI/MSI-X 中断
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_pcie_probe(struct platform_device *pdev)
{
    struct rockchip_pcie *rockchip;
    struct dw_pcie *pci;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    rockchip = devm_kzalloc(&pdev->dev, sizeof(*rockchip), GFP_KERNEL);
    if (!rockchip)
        return -ENOMEM;
    platform_set_drvdata(pdev, rockchip);
    rockchip->dev = &pdev->dev;
    pci = &rockchip->pci;
    pci->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源 (核心寄存器)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    pci->dbi_base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(pci->dbi_base))
        return PTR_ERR(pci->dbi_base);
​
    // 3. 获取应用层寄存器 (Rockchip 扩展)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (!res) {
        dev_err(&pdev->dev, "No app memory resource\n");
        return -ENXIO;
    }
    rockchip->app_base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(rockchip->app_base))
        return PTR_ERR(rockchip->app_base);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    rockchip->irq = irq;
​
    // 5. 获取时钟
    rockchip->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(rockchip->aclk))
        return PTR_ERR(rockchip->aclk);
    rockchip->aclk_perf = devm_clk_get(&pdev->dev, "aclk_perf");
    if (IS_ERR(rockchip->aclk_perf))
        return PTR_ERR(rockchip->aclk_perf);
    rockchip->hclk = devm_clk_get(&pdev->dev, "hclk");
    if (IS_ERR(rockchip->hclk))
        return PTR_ERR(rockchip->hclk);
    rockchip->pmclk = devm_clk_get_optional(&pdev->dev, "pmclk");
    if (IS_ERR(rockchip->pmclk))
        return PTR_ERR(rockchip->pmclk);
​
    // 6. 获取 PHY
    rockchip->phy = devm_phy_get(&pdev->dev, "pcie-phy");
    if (IS_ERR(rockchip->phy))
        return PTR_ERR(rockchip->phy);
​
    // 7. 获取复位
    rockchip->rst = devm_reset_control_get(&pdev->dev, "pcie");
    if (IS_ERR(rockchip->rst))
        return PTR_ERR(rockchip->rst);
​
    // 8. 读取 DTS 参数 (链路速度、通道数)
    of_property_read_u32(pdev->dev.of_node, "link-gen", &rockchip->link_gen);
    of_property_read_u32(pdev->dev.of_node, "lanes", &rockchip->lanes);
​
    // 9. 初始化 DWC PCIe 核心
    pci->ops = &rockchip_pcie_ops;
    pci->link_up = rockchip_pcie_link_up;
    pci->start_link = rockchip_pcie_start_link;
    pci->stop_link = rockchip_pcie_stop_link;
    pci->num_lanes = rockchip->lanes;
​
    // 10. 硬件初始化
    ret = rockchip_pcie_hw_init(rockchip);
    if (ret) {
        dev_err(&pdev->dev, "Failed to initialize hardware\n");
        return ret;
    }
​
    // 11. 注册中断
    ret = devm_request_irq(&pdev->dev, rockchip->irq, rockchip_pcie_irq_handler,
                           IRQF_SHARED, "rockchip-pcie", rockchip);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 12. 建立 PCIe 总线并注册主机桥
    rockchip->bridge = devm_pci_alloc_host_bridge(&pdev->dev, sizeof(*rockchip));
    if (!rockchip->bridge)
        return -ENOMEM;
​
    // 填充主机桥参数
    rockchip->bridge->ops = &rockchip_pcie_host_ops;
    rockchip->bridge->busnr = 0;
    rockchip->bridge->dev.parent = &pdev->dev;
​
    ret = pci_host_bridge_register(rockchip->bridge);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register host bridge\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "Rockchip PCIe controller registered at 0x%llx, IRQ %d, Gen%d, %d lanes\n",
             (unsigned long long)res->start, rockchip->irq, rockchip->link_gen, rockchip->lanes);
    return 0;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_pcie_remove(struct platform_device *pdev)
{
    struct rockchip_pcie *rockchip = platform_get_drvdata(pdev);
​
    // 1. 停止链路
    rockchip_pcie_stop_link(&rockchip->pci);
​
    // 2. 禁用时钟
    if (rockchip->pmclk)
        clk_disable_unprepare(rockchip->pmclk);
    clk_disable_unprepare(rockchip->hclk);
    clk_disable_unprepare(rockchip->aclk_perf);
    clk_disable_unprepare(rockchip->aclk);
​
    // 3. 复位控制器
    reset_control_assert(rockchip->rst);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_pcie_of_match[] = {
    { .compatible = "rockchip,rk3399-pcie" },
    { .compatible = "rockchip,rk3568-pcie" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pcie_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_pcie_driver = {
    .probe = rockchip_pcie_probe,
    .remove = rockchip_pcie_remove,
    .driver = {
        .name = "rockchip-pcie",
        .of_match_table = rockchip_pcie_of_match,
    },
};
module_platform_driver(rockchip_pcie_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip PCIe Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 PCIe 调试核心难点

3.1 链路训练失败 (Link training failed)

现象:PCIe 设备无法识别,lspci 无输出,dmesg 显示 "PCIe link training timeout"。

原因

  • PHY 初始化失败或信号质量差。

  • 时钟不稳定 (REFCLK 缺失或漂移)。

  • 电源电压不稳。

  • 设备树配置错误 (lane 数量、速度不匹配)。

调试方法

  1. 检查 PHY 状态

    # 查看 PHY 寄存器
    devmem2 <phy_base_addr>
    # 检查 PHY 锁定状态 (通常有状态寄存器)
  2. 检查时钟

    cat /sys/kernel/debug/clk/clk_summary | grep pcie
  3. 强制链路训练

    # 使用 devmem2 强制启用链路训练
    devmem2 <app_base+0x00> 0x01
  4. 查看链路状态寄存器

    devmem2 <app_base+0x10>  # 读取链路状态
  5. 降低链路速度:在 DTS 中设置 link-gen = <1> (Gen1),排除高速信号问题。

3.2 设备枚举后无法访问配置空间

现象lspci 能列出设备,但访问设备时 (lspci -v) 报告 "Configuration space access failed"。

原因

  • 配置空间映射错误。

  • 设备没有正确响应配置请求。

  • 链路状态不稳定。

调试方法

  1. 测试配置空间访问

    # 使用 setpci 手动读取配置空间
    setpci -s 00:00.0 COMMAND
  2. 检查总线设备映射

    cat /sys/bus/pci/devices/0000:00:00.0/device
  3. 检查 PCIe 核心配置

    cat /sys/kernel/debug/pci/0000:00:00.0/config

3.3 MSI/MSI-X 中断不工作

现象:设备驱动能加载,但中断处理函数从不被调用。

原因

  • MSI/MSI-X 使能未设置。

  • 中断控制器 (GIC) 未正确映射 MSI 中断。

  • 设备未配置 MSI/MSI-X 能力。

调试方法

  1. 检查 MSI 使能

    # 查看设备的 MSI 能力
    setpci -s 00:00.0 MSI_CTRL
  2. 查看 MSI 中断映射

    cat /proc/interrupts | grep pci
  3. 强制使用传统中断

    # 在驱动中禁用 MSI
    pci_disable_msi(pdev)

3.4 PCIe 链路不稳定性 (PCIe Link flapping)

现象:PCIe 设备反复断开重连,dmesg 显示 "PCIe link down" 然后 "PCIe link up"。

原因

  • 信号完整性差。

  • 电源噪声。

  • 温度变化导致链路不稳定。

调试方法

  1. 查看链路状态统计

    cat /sys/kernel/debug/dwc3/dwc3.0/regdump
  2. 降低链路速度:尝试使用 Gen1。

  3. 检查 PHY 信号质量:使用示波器查看 PCIe 差分信号。


第四章 结合性能调试场景示例

场景:通过 NVMe SSD 读取大文件时,性能只有 200MB/s,预期达到 3GB/s。

分析流程

  1. 宏观层面(图谱的 CPU 和 I/O 层):

    • iostat -x 1 显示 nvme0n1%util 一直 100%,但吞吐量远低于预期。

    • perf top -G 显示 rockchip_pcie_irq_handlerdma_sync_single_for_cpu 占用大量 CPU。

  2. PCIe 层(图谱的 Device Drivers -> PCIe Controller 层):

    lspci -vv -s 01:00.0

    发现设备支持 PCIe Gen3 x4,但当前只工作在 Gen2 x2。

  3. 跟踪 DMA 事件(DMA 控制器层):

    trace-cmd record -e pci:* -e dma:* -a -- timeout 5
    trace-cmd report | grep -E "pcie|dma"

    发现 DMA 中断频繁,中断处理延迟高。

  4. 根本原因

    • 链路工作在 Gen2 x2 模式,理论带宽约 1000MB/s,实际带宽远低于理论值。

    • NVMe 驱动使用大量的小块 IO,每次 IO 都触发 PCIe 中断,中断处理占用了 CPU 资源。

    • 同时 dma_sync_single_for_cpu 被频繁调用,造成缓存刷新开销。

  5. 解决方案

    • 在 DTS 中强制链路速度为 Gen3 x4。

    • 调整 IO 合并策略,合并小 IO 为大块 IO。

    • 增加 IO 队列深度 (nr_requests) 和请求合并 (merge).

    • 使用 nvme_core.io_timeout=100 设置更长的超时。

    • 优化后,吞吐量提升到 2.8GB/s。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
PHY 物理层收发器 信号质量、链路训练、速度协商
DMA 数据传输搬运 DMA 描述符链、中断聚合
GPIO 设备复位、电源使能 复位时序,电平电压
PMC 电源管理、链路状态 链路休眠恢复、PME 唤醒
Clock Controller 提供 REFCLK 和总线时钟 时钟稳定性与链路速率匹配

第九部分 NAND 控制器

第一章 NAND 控制器在 Platform Bus 中的位置

NAND 控制器(NAND Flash Controller)是 SoC 内部用于管理 NAND Flash 存储芯片的硬件模块。它负责处理 NAND Flash 的命令序列、地址控制、数据读写(包括 ECC 校验)、坏块管理等复杂逻辑。NAND 控制器通常通过 Platform Bus 挂载在 SoC 内部总线上。

Linux 中 NAND 子系统的核心架构分为三层:

  1. NAND 核心层 (drivers/mtd/nand/): 提供通用的 NAND 芯片抽象 (struct nand_chip),处理 ECC、坏块管理、BBT (Bad Block Table) 等。

  2. NAND 控制器驱动(本篇文章重点):具体的 SoC NAND 控制器驱动(如 sunxi_nand.cgpmi-nand.cmxc_nand.c)。

  3. MTD 层 (drivers/mtd/): 将 NAND Flash 抽象为 /dev/mtdX 字符设备和 /dev/mtdblockX 块设备,供文件系统(UBIFS、JFFS2)使用。


第二章 Linux 5.10 典型 NAND 控制器驱动 —— sunxi_nand.c

Allwinner (SUNXI) NAND 控制器是典型的挂载在 Platform Bus 上的 NAND Flash 控制器,广泛用于 H3、A64、H6 等 SoC。它支持 SLC 和 MLC NAND,内置硬件 ECC (BCH 或 Hamming),并支持 DMA 数据传输。以下代码基于 Linux 5.10 drivers/mtd/nand/raw/sunxi_nand.c,展示核心逻辑。

2.1 硬件关键概念

  • NAND 命令寄存器:用于发送命令、地址、数据。

  • DMA 引擎:支持通过 DMA 进行数据传输,提高性能。

  • ECC 引擎:内置 BCH 或 Hamming ECC 硬件,用于错误检测和纠正。

  • 时钟:通常需要 ahb_clk(总线时钟)、mod_clk(NAND 模块时钟)。

  • 中断:处理 DMA 完成、ECC 错误等事件。

2.2 核心代码

// 基于 Linux 5.10 drivers/mtd/nand/raw/sunxi_nand.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
​
/**
 * @brief Allwinner NAND 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 NAND Controller 硬件实例。
 */
struct sunxi_nand {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    int irq;                         /**< NAND 中断号 */
    struct clk *ahb_clk;             /**< AHB 总线时钟 */
    struct clk *mod_clk;             /**< 模块时钟 */
    struct dma_chan *dma_tx;         /**< DMA 发送通道 */
    struct dma_chan *dma_rx;         /**< DMA 接收通道 */
    struct nand_chip nand;           /**< NAND 芯片抽象 */
    struct mtd_info mtd;             /**< MTD 设备抽象 */
    struct device *dev;              /**< 设备指针 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    u8 *buffer;                      /**< 数据缓存 */
    dma_addr_t buffer_dma;           /**< 缓存 DMA 地址 */
    int ecc_strength;                /**< ECC 强度 (4/8/16/24/32/40/48/56/60/64) */
    int ecc_size;                    /**< ECC 步长 (512/1024) */
};
​
/* 寄存器偏移量 (Allwinner NAND) */
#define NAND_CMD_REG                0x00
#define NAND_ADDR_REG               0x04
#define NAND_DATA_REG               0x08
#define NAND_CTL_REG                0x0C
#define NAND_STAT_REG               0x10
#define NAND_INT_STAT_REG           0x14
#define NAND_INT_EN_REG             0x18
#define NAND_DMA_CTL_REG            0x1C
#define NAND_ECC_CTL_REG            0x20
#define NAND_ECC_STAT_REG           0x24
#define NAND_DMA_ADDR_REG           0x28
​
/**
 * @brief 发送 NAND 命令。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param cmd 命令码。
 */
static void sunxi_nand_cmd(struct nand_chip *nand, u8 cmd)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    unsigned long flags;
​
    spin_lock_irqsave(&sunxi_nand->lock, flags);
    // 写入命令寄存器
    writel(cmd, sunxi_nand->base + NAND_CMD_REG);
    // 等待命令完成 (状态寄存器相应位)
    while (!(readl(sunxi_nand->base + NAND_STAT_REG) & (1 << 0)))
        ;
    spin_unlock_irqrestore(&sunxi_nand->lock, flags);
}
​
/**
 * @brief 发送 NAND 地址。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param addr 地址值。
 */
static void sunxi_nand_addr(struct nand_chip *nand, u8 addr)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    writel(addr, sunxi_nand->base + NAND_ADDR_REG);
}
​
/**
 * @brief 从 NAND 读取一个字节。
 * 
 * @param nand 指向 nand_chip 结构。
 * @return 读取的字节。
 */
static u8 sunxi_nand_read_byte(struct nand_chip *nand)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    return readl(sunxi_nand->base + NAND_DATA_REG) & 0xFF;
}
​
/**
 * @brief 向 NAND 写入一个字节。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param data 要写入的字节。
 */
static void sunxi_nand_write_byte(struct nand_chip *nand, u8 data)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    writel(data, sunxi_nand->base + NAND_DATA_REG);
}
​
/**
 * @brief 读取 NAND 状态。
 * 
 * @param nand 指向 nand_chip 结构。
 * @return 状态字节。
 */
static u8 sunxi_nand_status(struct nand_chip *nand)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    return readl(sunxi_nand->base + NAND_STAT_REG) & 0xFF;
}
​
/**
 * @brief 读取 NAND 芯片 ID。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param id 输出 ID 缓冲区。
 * @param len 读取的长度。
 */
static void sunxi_nand_read_id(struct nand_chip *nand, u8 *id, int len)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    int i;
​
    for (i = 0; i < len; i++) {
        id[i] = readl(sunxi_nand->base + NAND_DATA_REG) & 0xFF;
    }
}
​
/**
 * @brief 通过 DMA 读取数据页。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param data 数据缓冲区。
 * @param len 数据长度。
 */
static int sunxi_nand_dma_read(struct nand_chip *nand, u8 *data, int len)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    struct dma_async_tx_descriptor *tx;
    dma_addr_t dma_addr;
    int ret = 0;
​
    // 1. 映射用户缓冲区到 DMA 地址
    dma_addr = dma_map_single(sunxi_nand->dev, data, len, DMA_FROM_DEVICE);
    if (dma_mapping_error(sunxi_nand->dev, dma_addr)) {
        dev_err(sunxi_nand->dev, "Failed to map DMA buffer\n");
        return -ENOMEM;
    }
​
    // 2. 配置 DMA 接收通道
    struct dma_slave_config config;
    memset(&config, 0, sizeof(config));
    config.direction = DMA_DEV_TO_MEM;
    config.src_addr = (dma_addr_t)sunxi_nand->base + NAND_DATA_REG;
    config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
    config.src_maxburst = 32;
    dmaengine_slave_config(sunxi_nand->dma_rx, &config);
​
    // 3. 创建 DMA 描述符
    tx = dmaengine_prep_slave_single(sunxi_nand->dma_rx, dma_addr, len,
                                      DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
    if (!tx) {
        dev_err(sunxi_nand->dev, "Failed to prepare DMA descriptor\n");
        dma_unmap_single(sunxi_nand->dev, dma_addr, len, DMA_FROM_DEVICE);
        return -ENOMEM;
    }
​
    // 4. 启动 DMA
    dmaengine_submit(tx);
    dma_async_issue_pending(sunxi_nand->dma_rx);
​
    // 5. 等待 DMA 完成
    wait_for_completion(&sunxi_nand->dma_complete);
​
    // 6. 取消映射
    dma_unmap_single(sunxi_nand->dev, dma_addr, len, DMA_FROM_DEVICE);
​
    return ret;
}
​
/**
 * @brief 通过 DMA 写入数据页。
 * 
 * @param nand 指向 nand_chip 结构。
 * @param data 数据缓冲区。
 * @param len 数据长度。
 */
static int sunxi_nand_dma_write(struct nand_chip *nand, u8 *data, int len)
{
    struct sunxi_nand *sunxi_nand = container_of(nand, struct sunxi_nand, nand);
    struct dma_async_tx_descriptor *tx;
    dma_addr_t dma_addr;
    int ret = 0;
​
    // 1. 映射用户缓冲区到 DMA 地址
    dma_addr = dma_map_single(sunxi_nand->dev, data, len, DMA_TO_DEVICE);
    if (dma_mapping_error(sunxi_nand->dev, dma_addr)) {
        dev_err(sunxi_nand->dev, "Failed to map DMA buffer\n");
        return -ENOMEM;
    }
​
    // 2. 配置 DMA 发送通道
    struct dma_slave_config config;
    memset(&config, 0, sizeof(config));
    config.direction = DMA_MEM_TO_DEV;
    config.dst_addr = (dma_addr_t)sunxi_nand->base + NAND_DATA_REG;
    config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
    config.dst_maxburst = 32;
    dmaengine_slave_config(sunxi_nand->dma_tx, &config);
​
    // 3. 创建 DMA 描述符
    tx = dmaengine_prep_slave_single(sunxi_nand->dma_tx, dma_addr, len,
                                      DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
    if (!tx) {
        dev_err(sunxi_nand->dev, "Failed to prepare DMA descriptor\n");
        dma_unmap_single(sunxi_nand->dev, dma_addr, len, DMA_TO_DEVICE);
        return -ENOMEM;
    }
​
    // 4. 启动 DMA
    dmaengine_submit(tx);
    dma_async_issue_pending(sunxi_nand->dma_tx);
​
    // 5. 等待 DMA 完成
    wait_for_completion(&sunxi_nand->dma_complete);
​
    // 6. 取消映射
    dma_unmap_single(sunxi_nand->dev, dma_addr, len, DMA_TO_DEVICE);
​
    return ret;
}
​
/**
 * @brief 中断处理函数。
 * 
 * 处理 DMA 完成、ECC 错误等中断事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 sunxi_nand 结构。
 */
static irqreturn_t sunxi_nand_irq_handler(int irq, void *dev_id)
{
    struct sunxi_nand *sunxi_nand = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(sunxi_nand->base + NAND_INT_STAT_REG);
​
    // 2. 处理 DMA 完成中断
    if (int_stat & (1 << 0)) {
        // 清除中断
        writel(1 << 0, sunxi_nand->base + NAND_INT_STAT_REG);
        // 通知等待 DMA 完成的线程
        complete(&sunxi_nand->dma_complete);
    }
​
    // 3. 处理 ECC 错误中断
    if (int_stat & (1 << 1)) {
        // 读取 ECC 状态
        u32 ecc_stat = readl(sunxi_nand->base + NAND_ECC_STAT_REG);
        dev_err(sunxi_nand->dev, "ECC error detected: 0x%x\n", ecc_stat);
        // 清除中断
        writel(1 << 1, sunxi_nand->base + NAND_INT_STAT_REG);
    }
​
    // 4. 处理其他错误中断
    if (int_stat & (1 << 2)) {
        dev_err(sunxi_nand->dev, "NAND controller error\n");
        writel(1 << 2, sunxi_nand->base + NAND_INT_STAT_REG);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 初始化 ECC 硬件。
 * 
 * @param sunxi_nand 指向 sunxi_nand 结构。
 */
static void sunxi_nand_ecc_init(struct sunxi_nand *sunxi_nand)
{
    u32 ecc_ctl;
​
    // 1. 配置 ECC 强度 (从 DTS 读取或根据 NAND 类型自动选择)
    sunxi_nand->ecc_strength = 8;
    sunxi_nand->ecc_size = 512;
​
    // 2. 配置 ECC 控制寄存器
    ecc_ctl = (sunxi_nand->ecc_strength << 8) | (sunxi_nand->ecc_size << 0);
    writel(ecc_ctl, sunxi_nand->base + NAND_ECC_CTL_REG);
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 NAND 控制器硬件,注册 NAND 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int sunxi_nand_probe(struct platform_device *pdev)
{
    struct sunxi_nand *sunxi_nand;
    struct nand_chip *nand;
    struct mtd_info *mtd;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    sunxi_nand = devm_kzalloc(&pdev->dev, sizeof(*sunxi_nand), GFP_KERNEL);
    if (!sunxi_nand)
        return -ENOMEM;
    platform_set_drvdata(pdev, sunxi_nand);
    sunxi_nand->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    sunxi_nand->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(sunxi_nand->base))
        return PTR_ERR(sunxi_nand->base);
​
    // 3. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    sunxi_nand->irq = irq;
​
    // 4. 获取时钟
    sunxi_nand->ahb_clk = devm_clk_get(&pdev->dev, "ahb");
    if (IS_ERR(sunxi_nand->ahb_clk))
        return PTR_ERR(sunxi_nand->ahb_clk);
    sunxi_nand->mod_clk = devm_clk_get(&pdev->dev, "mod");
    if (IS_ERR(sunxi_nand->mod_clk))
        return PTR_ERR(sunxi_nand->mod_clk);
​
    clk_prepare_enable(sunxi_nand->ahb_clk);
    clk_prepare_enable(sunxi_nand->mod_clk);
​
    // 5. 获取 DMA 通道
    sunxi_nand->dma_tx = dma_request_chan(sunxi_nand->dev, "tx");
    if (IS_ERR(sunxi_nand->dma_tx)) {
        dev_err(sunxi_nand->dev, "Failed to get TX DMA channel\n");
        ret = PTR_ERR(sunxi_nand->dma_tx);
        goto err_clk;
    }
    sunxi_nand->dma_rx = dma_request_chan(sunxi_nand->dev, "rx");
    if (IS_ERR(sunxi_nand->dma_rx)) {
        dev_err(sunxi_nand->dev, "Failed to get RX DMA channel\n");
        ret = PTR_ERR(sunxi_nand->dma_rx);
        goto err_dma_tx;
    }
​
    // 6. 初始化 DMA 完成量
    init_completion(&sunxi_nand->dma_complete);
​
    spin_lock_init(&sunxi_nand->lock);
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, sunxi_nand->irq, sunxi_nand_irq_handler,
                           IRQF_SHARED, "sunxi-nand", sunxi_nand);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_dma_rx;
    }
​
    // 8. 初始化 NAND 芯片结构
    nand = &sunxi_nand->nand;
    mtd = nand_to_mtd(nand);
    mtd->dev.parent = &pdev->dev;
    mtd->name = "sunxi-nand";
    mtd->type = MTD_NANDFLASH;
​
    // 设置 NAND 操作回调
    nand->cmd = sunxi_nand_cmd;
    nand->addr = sunxi_nand_addr;
    nand->read_byte = sunxi_nand_read_byte;
    nand->write_byte = sunxi_nand_write_byte;
    nand->read_id = sunxi_nand_read_id;
    nand->status = sunxi_nand_status;
​
    // 9. 配置 ECC
    sunxi_nand_ecc_init(sunxi_nand);
​
    // 10. 扫描 NAND 芯片
    ret = nand_scan(nand, 1);
    if (ret) {
        dev_err(&pdev->dev, "Failed to scan NAND chip\n");
        goto err_irq;
    }
​
    // 11. 注册 MTD 设备
    ret = mtd_device_register(mtd, NULL, 0);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register MTD device\n");
        goto err_nand_cleanup;
    }
​
    dev_info(&pdev->dev, "Sunxi NAND controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, sunxi_nand->irq);
    return 0;
​
err_nand_cleanup:
    nand_cleanup(nand);
err_irq:
    devm_free_irq(&pdev->dev, sunxi_nand->irq, sunxi_nand);
err_dma_rx:
    dma_release_channel(sunxi_nand->dma_rx);
err_dma_tx:
    dma_release_channel(sunxi_nand->dma_tx);
err_clk:
    clk_disable_unprepare(sunxi_nand->mod_clk);
    clk_disable_unprepare(sunxi_nand->ahb_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int sunxi_nand_remove(struct platform_device *pdev)
{
    struct sunxi_nand *sunxi_nand = platform_get_drvdata(pdev);
    struct nand_chip *nand = &sunxi_nand->nand;
​
    // 1. 注销 MTD 设备
    mtd_device_unregister(nand_to_mtd(nand));
​
    // 2. 清理 NAND 芯片
    nand_cleanup(nand);
​
    // 3. 释放 DMA 通道
    dma_release_channel(sunxi_nand->dma_rx);
    dma_release_channel(sunxi_nand->dma_tx);
​
    // 4. 禁用时钟
    clk_disable_unprepare(sunxi_nand->mod_clk);
    clk_disable_unprepare(sunxi_nand->ahb_clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id sunxi_nand_of_match[] = {
    { .compatible = "allwinner,sun8i-a23-nand-controller" },
    { .compatible = "allwinner,sun8i-h3-nand-controller" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_nand_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver sunxi_nand_driver = {
    .probe = sunxi_nand_probe,
    .remove = sunxi_nand_remove,
    .driver = {
        .name = "sunxi-nand",
        .of_match_table = sunxi_nand_of_match,
    },
};
module_platform_driver(sunxi_nand_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Allwinner NAND Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 NAND 调试核心难点

3.1 NAND 芯片无法识别

现象cat /proc/mtd 显示为空,dmesg 无 NAND 设备输出。

原因

  • NAND 时序配置错误(不同厂商的 NAND 需要不同的时序)。

  • 电源未使能。

  • 控制器初始化失败。

调试方法

  1. 检查 NAND 芯片 ID

    # 手动读取 NAND ID
    devmem2 <base_addr>+0x08  # 读取 ID 寄存器
  2. 检查时序配置:在 DTS 中设置 nand-ecc-strengthnand-ecc-step-size 等。

  3. 硬件检查:使用逻辑分析仪查看 NAND 接口是否有信号。

3.2 ECC 错误导致数据损坏

现象:读取 NAND 数据时,大量 ECC 错误报告,或者读出的数据不正确。

原因

  • ECC 算法选择错误(SLC NAND 使用 Hamming,MLC NAND 使用 BCH)。

  • ECC 强度和步长与 NAND 芯片不匹配。

  • NAND 芯片老化或坏块。

调试方法

  1. 查看 ECC 统计

    cat /sys/devices/platform/sunxi-nand/mtd0/ecc_stats
  2. 手动配置 ECC

    # 强制使用 8-bit BCH
    echo "bch" > /sys/devices/platform/sunxi-nand/mtd0/ecc_alg

3.3 DMA 传输失败

现象:NAND 读写速度慢,或者 DMA 传输一直卡住。

原因

  • DMA 通道配置错误。

  • DMA 缓冲区地址未对齐。

  • 中断没有正常触发。

调试方法

  1. 检查 DMA 通道状态

    cat /sys/kernel/debug/dma/dma-* | grep nand
  2. 使用 perf 监控 DMA 中断

    perf record -e dma:* -a -- timeout 5
    perf script | grep sunxi_nand
  3. 使用 PIO 模式:在驱动中强制禁用 DMA,测试 PIO 模式是否正常。

3.4 坏块管理问题

现象ubiformat 格式化时报告大量坏块,或文件系统挂载后频繁报错。

原因

  • 坏块表 (BBT) 损坏。

  • NAND 芯片出厂时已有坏块。

  • 控制器没有正确处理坏块标记。

调试方法

  1. 查看坏块表

    cat /sys/devices/platform/sunxi-nand/mtd0/bad_blocks
  2. 手动标记坏块

    # 标记块为坏块
    echo "block 512" > /sys/devices/platform/sunxi-nand/mtd0/mark_bad_block

第四章 结合性能调试场景示例

场景:UBIFS 挂载后,读取大文件时速度仅 2MB/s,远低于预期。

分析流程

  1. 宏观层面(图谱的 CPU 和 I/O 层):

    • iostat -x 1 显示 mtd0svctm 很高。

    • perf top -G 显示 sunxi_nand_irq_handlerdma_sync_single_for_cpu 占用大量 CPU。

  2. NAND 层(图谱的 Device Drivers -> NAND Controller 层):

    cat /sys/kernel/debug/mtd/mtd0/registers

    发现 ECC 强度设置为 4-bit,但 NAND 芯片本身是 8-bit ECC。

  3. 检查 DMA 配置(DMA 控制器层):

    trace-cmd record -e dma:* -e mtd:* -a -- timeout 5
    trace-cmd report | grep sunxi_nand

    发现 DMA 中断频繁,每次中断处理开销大。

  4. 根本原因

    • ECC 强度配置错误,导致每次读取都需要 2 次 ECC 计算。

    • DMA 中断触发频率过高(每个 block 触发一次)。

  5. 解决方案

    • 在 DTS 中强制设置正确的 ECC 强度 nand-ecc-strength = <8>;

    • 调整 DMA 中断聚合,合并多个 block 的中断。

    • 使用更大的 NAND 页大小 (4KB 或 8KB)。

    • 优化后,读取速度提升到 25MB/s。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 数据传输搬运 DMA 通道分配、中断聚合
时钟 提供模块时钟 时钟频率与 NAND 时序匹配
GPIO 控制 NAND 的 WP (写保护) 和 RB (Ready/Busy) GPIO 电平配置,写保护屏蔽
PMIC 提供电源电压 电源稳定,电压容差
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐