第一部分 SDHCI 控制器 (DesignWare DWC MSHC)

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

SDHCI 是 SD 卡和 eMMC 的标准主机接口。DWC MSHC (Mobile Storage Host Controller) 是 Synopsys 的 SDHCI 兼容 IP 核,广泛用于 Rockchip、Allwinner 等 SoC。它提供与标准 SDHCI 相同的寄存器接口,并添加了 SoC 特定的配置。

在 Linux 5.10 中,SDHCI 框架分为三层:

  1. SDHCI 核心层 (drivers/mmc/host/sdhci.c):提供标准的 SDHCI 操作(命令、数据、中断、DMA)。

  2. SDHCI 平台驱动(本篇文章重点):具体 SoC 的 SDHCI 驱动(如 sdhci-of-dwcmshc.c)。

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

1.1 硬件关键概念

  • SDHCI 标准寄存器:控制寄存器、状态寄存器、命令寄存器、数据寄存器。

  • DMA 引擎:支持 ADMA2 (Advanced DMA) 模式,使用描述符链。

  • 时钟:需要 ahb_clk(总线时钟)和 core_clk(SD 核心时钟)。

  • 中断:处理命令完成、数据完成、错误等事件。

  • 卡检测:通过 GPIO 或 SDHCI 寄存器检测卡插入。

1.2 核心代码

// 基于 Linux 5.10 drivers/mmc/host/sdhci-of-dwcmshc.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/dma-mapping.h>
#include "sdhci-pltfm.h"
​
/**
 * @brief DWC MSHC SDHCI 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 SDHCI 硬件实例。
 */
struct dwcmshc_priv {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    int irq;                       /**< 中断号 */
    struct clk *ahb_clk;           /**< AHB 总线时钟 */
    struct clk *core_clk;          /**< SD 核心时钟 */
    struct clk *bus_clk;           /**< 总线时钟 */
    struct sdhci_host *host;       /**< SDHCI 主机抽象 */
    struct device *dev;            /**< 设备指针 */
    spinlock_t lock;               /**< 硬件保护锁 */
    u32 version;                   /**< 控制器版本 */
    struct sdhci_pltfm_data *pdata; /**< 平台特定数据 */
};
​
/* 寄存器偏移量 (DWC MSHC 扩展) */
#define DWC_MSHC_CTRL               0x30
#define DWC_MSHC_CLK_SRC            0x34
#define DWC_MSHC_PWR_EN             0x38
#define DWC_MSHC_TMOUT              0x3C
#define DWC_MSHC_BLK_CNT            0x40
#define DWC_MSHC_AXI_CTRL           0x44
#define DWC_MSHC_CAP                0x48
#define DWC_MSHC_ADMA_CTRL          0x4C
#define DWC_MSHC_ADMA_LOW           0x50
#define DWC_MSHC_ADMA_HIGH          0x54
​
/**
 * @brief 初始化 DWC MSHC 硬件。
 * 
 * @param priv 指向 dwcmshc_priv 结构。
 */
static void dwcmshc_hw_init(struct dwcmshc_priv *priv)
{
    u32 ctrl;
​
    // 1. 启用 ADMA2 模式
    ctrl = readl(priv->base + DWC_MSHC_ADMA_CTRL);
    ctrl |= (1 << 0);
    writel(ctrl, priv->base + DWC_MSHC_ADMA_CTRL);
​
    // 2. 设置 ADMA 描述符表地址 (由 sdhci 核心分配)
    dma_addr_t adma_addr = sdhci_get_adma_addr(priv->host);
    writel(lower_32_bits(adma_addr), priv->base + DWC_MSHC_ADMA_LOW);
    writel(upper_32_bits(adma_addr), priv->base + DWC_MSHC_ADMA_HIGH);
​
    // 3. 配置 AXI 总线控制 (提升性能)
    ctrl = readl(priv->base + DWC_MSHC_AXI_CTRL);
    ctrl |= (0x03 << 16);  // 增加 AXI 突发长度
    writel(ctrl, priv->base + DWC_MSHC_AXI_CTRL);
}
​
/**
 * @brief 设置 SD 时钟频率。
 * 
 * @param host 指向 sdhci_host 结构。
 * @param clock 目标时钟频率 (Hz)。
 */
static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct dwcmshc_priv *priv = sdhci_priv(host);
    unsigned long clk_rate;
    u32 div, ctrl;
​
    if (clock == 0) {
        // 关闭时钟
        writel(0, priv->base + DWC_MSHC_CLK_SRC);
        return;
    }
​
    clk_rate = clk_get_rate(priv->core_clk);
    div = clk_rate / clock;
    if (div < 1) div = 1;
    if (div > 0xFF) div = 0xFF;
​
    // 设置分频器
    ctrl = readl(priv->base + DWC_MSHC_CLK_SRC);
    ctrl &= ~0xFF;
    ctrl |= div;
    writel(ctrl, priv->base + DWC_MSHC_CLK_SRC);
​
    // 启用时钟
    ctrl |= (1 << 8);
    writel(ctrl, priv->base + DWC_MSHC_CLK_SRC);
}
​
/**
 * @brief SDHCI 平台探测函数。
 * 
 * 初始化 SDHCI 控制器硬件,注册到 MMC 核心。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dwcmshc_probe(struct platform_device *pdev)
{
    struct dwcmshc_priv *priv;
    struct sdhci_host *host;
    struct resource *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 资源
    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. 获取时钟
    priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb");
    if (IS_ERR(priv->ahb_clk))
        return PTR_ERR(priv->ahb_clk);
    priv->core_clk = devm_clk_get(&pdev->dev, "core");
    if (IS_ERR(priv->core_clk))
        return PTR_ERR(priv->core_clk);
    priv->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
    if (IS_ERR(priv->bus_clk))
        return PTR_ERR(priv->bus_clk);
​
    clk_prepare_enable(priv->ahb_clk);
    clk_prepare_enable(priv->core_clk);
    if (priv->bus_clk)
        clk_prepare_enable(priv->bus_clk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    priv->irq = irq;
​
    // 5. 初始化 SDHCI 主机
    host = sdhci_pltfm_init(pdev, &priv->pdata, sizeof(*priv));
    if (IS_ERR(host)) {
        ret = PTR_ERR(host);
        goto err_clk;
    }
    priv->host = host;
    host->mmc->parent = &pdev->dev;
​
    // 6. 设置 SDHCI 操作回调
    host->ops = &dwcmshc_ops;
    host->mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
    host->mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23;
    host->mmc->max_seg_size = PAGE_SIZE * 64;
    host->mmc->max_req_size = PAGE_SIZE * 64;
​
    spin_lock_init(&priv->lock);
​
    // 7. 硬件初始化
    dwcmshc_hw_init(priv);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, priv->irq, sdhci_irq_handler,
                           IRQF_SHARED, "dwcmshc", host);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_sdhci;
    }
​
    // 9. 注册 MMC 主机
    ret = sdhci_add_host(host);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add MMC host\n");
        goto err_sdhci;
    }
​
    dev_info(&pdev->dev, "DWC MSHC SDHCI controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, priv->irq);
    return 0;
​
err_sdhci:
    sdhci_pltfm_free(pdev);
err_clk:
    if (priv->bus_clk)
        clk_disable_unprepare(priv->bus_clk);
    clk_disable_unprepare(priv->core_clk);
    clk_disable_unprepare(priv->ahb_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dwcmshc_remove(struct platform_device *pdev)
{
    struct dwcmshc_priv *priv = platform_get_drvdata(pdev);
​
    // 1. 移除 SDHCI 主机
    sdhci_remove_host(priv->host, 0);
​
    // 2. 释放 SDHCI 平台资源
    sdhci_pltfm_free(pdev);
​
    // 3. 禁用时钟
    if (priv->bus_clk)
        clk_disable_unprepare(priv->bus_clk);
    clk_disable_unprepare(priv->core_clk);
    clk_disable_unprepare(priv->ahb_clk);
​
    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("DesignWare DWC MSHC SDHCI Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 SDHCI 调试核心难点

3.1 卡检测失败

现象:插入 SD 卡后,系统无任何响应,lsblk 不显示设备。

原因

  • 卡检测 GPIO 未正确配置。

  • 卡供电未使能。

  • 卡检测中断未触发。

调试方法

  1. 检查卡检测 GPIO

    cat /sys/kernel/debug/gpio | grep cd
  2. 强制检测卡

    echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/scan
  3. 检查卡供电

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

3.2 数据传输 CRC 错误

现象dmesg 频繁出现 "CRC error",读写速度慢。

原因

  • SD 总线信号质量差。

  • 时钟频率过高。

  • 布线问题。

调试方法

  1. 降低时钟频率

    # 在 DTS 中设置 max-frequency = <10000000>;
  2. 降低总线宽度

    echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/mmc0:0001/bus_width
  3. 检查信号完整性:用示波器查看 SD 时钟和数据线。

3.3 DMA 传输失败

现象:大量数据传输时出错,dmesg 显示 "DMA error"。

原因

  • ADMA 描述符表对齐错误。

  • DMA 地址超过 32 位范围。

  • DMA 中断处理超时。

调试方法

  1. 检查 ADMA 描述符

    devmem2 <base>+0x50  # ADMA_LOW
  2. 使用 PIO 模式测试:在驱动中强制禁用 DMA。

  3. 调试 DMA 中断

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

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

场景:使用 eMMC 读写文件时,速度只有 50MB/s,理论上应达 300MB/s。

分析流程

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

    • perf top 显示 sdhci_irq_handler 占用 CPU 约 30%。

    • iostat -x 1 显示 mmcblk0 的 r/sw/s 较高,但吞吐量低。

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

    cat /sys/kernel/debug/mmc0/ios

    发现当前频率为 50MHz,总线宽度为 4-bit,理论带宽约 200MB/s。

  3. 时钟配置(Clock 层):

    cat /sys/kernel/debug/clk/clk_summary | grep core

    发现 core_clk 频率仅为 100MHz。

  4. 根本原因

    • eMMC 芯片支持 HS200 模式(200MHz),但实际时钟被限制在 50MHz。

    • SDHCI 驱动中未正确启用 HS200 模式。

    • 未执行调优 (Tuning) 流程。

  5. 解决方案

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

    • 手动触发调优:echo 1 > /sys/kernel/debug/mmc0/execute_tuning

    • 调整时钟频率到 200MHz。

    • 优化后,读写速度提升到 280MB/s。


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

控制器 协同方式 调试关键点
PINCTRL 配置 SD 总线引脚功能 引脚复用、驱动强度
Clock Controller 提供 SD 核心时钟 频率匹配、电压调整
DMA 数据传输搬运 描述符链、中断聚合
Regulator 提供卡电源 (vmmc/vqmmc) 电压稳定、上电时序

第二部分 Reset 控制器 (Rockchip CRU)

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

Reset 控制器是 SoC 中最基础也最关键的系统组件之一。它负责管理芯片内部各个外设模块(如 UART、I2C、SPI、DMA、GPU、NPU 等)的复位信号。Linux 内核通过标准的 reset-controller 框架来统一管理这些复位信号。

在 Linux 5.10 中,Reset 控制器驱动通常位于 drivers/reset/,核心层次分为三层:

  1. Reset 核心层 (drivers/reset/core.c):提供复位控制器抽象、设备树解析和 API。

  2. Reset 控制器驱动(本篇文章重点):具体的 SoC Reset 控制器(如 reset-rockchip.c)。

  3. 复位客户端驱动:调用复位 API 的外设驱动(如 dw_i2c.c 中的 devm_reset_control_get)。

1.1 硬件关键概念

  • CRU (Clock and Reset Unit):管理时钟和复位的统一硬件单元。

  • 复位寄存器:每个外设通常有独立的复位控制位,写 0 复位,写 1 释放。

  • 复位类型:支持软复位(复位寄存器)和硬复位(复位引脚)。

  • 复位状态:某些控制器支持读取外设是否处于复位状态。

  • 复位序列:必须确保复位释放后,外设时钟也已使能。

1.2 核心代码

// 基于 Linux 5.10 drivers/reset/reset-rockchip.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/reset-controller.h>
#include <linux/of.h>
​
/**
 * @brief Rockchip Reset 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Reset Controller 硬件实例。
 */
struct rockchip_reset {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    struct device *dev;             /**< 设备指针 */
    struct reset_controller_dev rcdev; /**< 复位控制器核心抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 num_banks;                  /**< 寄存器组数量 */
    u32 *reg_offset;                /**< 各寄存器组偏移量 */
};
​
/**
 * @brief 复位一个外设。
 * 
 * 对应复位控制器框架的 ops -> reset 接口。
 *
 * @param rcdev 指向 reset_controller_dev 结构。
 * @param id 复位信号 ID。
 * @return 0 成功。
 */
static int rockchip_reset_reset(struct reset_controller_dev *rcdev,
                                unsigned long id)
{
    struct rockchip_reset *reset = container_of(rcdev, struct rockchip_reset, rcdev);
    u32 bank = id / 32;
    u32 bit = id % 32;
    u32 mask = 1 << bit;
    unsigned long flags;
​
    if (bank >= reset->num_banks) {
        dev_err(reset->dev, "Invalid reset ID %lu\n", id);
        return -EINVAL;
    }
​
    spin_lock_irqsave(&reset->lock, flags);
​
    // 1. 写 0 触发复位 (假设硬件定义为低电平有效)
    void __iomem *reg = reset->base + reset->reg_offset[bank];
    u32 val = readl(reg);
    val &= ~mask;
    writel(val, reg);
​
    // 2. 等待硬件完成复位 (通常需要几个时钟周期)
    udelay(1);
​
    // 3. 写 1 释放复位
    val |= mask;
    writel(val, reg);
​
    spin_unlock_irqrestore(&reset->lock, flags);
    return 0;
}
​
/**
 * @brief 获取复位状态。
 * 
 * @param rcdev 指向 reset_controller_dev。
 * @param id 复位信号 ID。
 * @return 1 处于复位状态,0 已释放。
 */
static int rockchip_reset_status(struct reset_controller_dev *rcdev,
                                 unsigned long id)
{
    struct rockchip_reset *reset = container_of(rcdev, struct rockchip_reset, rcdev);
    u32 bank = id / 32;
    u32 bit = id % 32;
    u32 mask = 1 << bit;
​
    if (bank >= reset->num_banks) {
        dev_err(reset->dev, "Invalid reset ID %lu\n", id);
        return -EINVAL;
    }
​
    void __iomem *reg = reset->base + reset->reg_offset[bank];
    u32 val = readl(reg);
    return (val & mask) ? 0 : 1; // 0 为复位状态 (假设低电平有效)
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Reset 控制器硬件,注册复位控制器。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_reset_probe(struct platform_device *pdev)
{
    struct rockchip_reset *reset;
    struct resource *res;
    int ret;
​
    // 1. 分配私有数据结构
    reset = devm_kzalloc(&pdev->dev, sizeof(*reset), GFP_KERNEL);
    if (!reset)
        return -ENOMEM;
    platform_set_drvdata(pdev, reset);
    reset->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;
    }
    reset->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(reset->base))
        return PTR_ERR(reset->base);
​
    // 3. 配置复位控制器核心抽象
    reset->rcdev.owner = THIS_MODULE;
    reset->rcdev.nr_resets = reset->num_banks * 32;
    reset->rcdev.ops = &rockchip_reset_ops;
    reset->rcdev.of_node = pdev->dev.of_node;
​
    spin_lock_init(&reset->lock);
​
    // 4. 注册复位控制器到核心框架
    ret = devm_reset_controller_register(&pdev->dev, &reset->rcdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register reset controller\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "Rockchip Reset controller registered at 0x%llx, %d resets\n",
             (unsigned long long)res->start, reset->rcdev.nr_resets);
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_reset_of_match[] = {
    { .compatible = "rockchip,rk3399-cru" },
    { .compatible = "rockchip,rk3568-cru" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_reset_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_reset_driver = {
    .probe = rockchip_reset_probe,
    .remove = rockchip_reset_remove,
    .driver = {
        .name = "rockchip-reset",
        .of_match_table = rockchip_reset_of_match,
    },
};
module_platform_driver(rockchip_reset_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Reset Controller Driver");
MODULE_LICENSE("GPL v2");

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

3.1 外设复位后无法正常工作

现象devm_reset_control_get 成功,但复位释放后外设仍然无法工作。

原因

  • 复位后未正确配置时钟。

  • 复位信号未正确释放(低电平保持)。

  • 硬件设计缺陷(复位引脚未连接)。

调试方法

  1. 检查复位状态

    devmem2 <base>+<bank_offset>  # 读取复位寄存器
  2. 强制释放复位

    # 直接写寄存器强制释放
    devmem2 <base>+<bank_offset> 0xFFFFFFFF
  3. 检查时钟

    # 确保外设时钟已使能
    cat /sys/kernel/debug/clk/clk_summary | grep <device_name>
  4. 检查复位序列:确保 resetudelaydeassert 之间有足够延时。

3.2 复位 ID 映射错误

现象:复位控制器的 ID 映射错误,导致复位了错误的外设,系统不稳定。

原因

  • DTS 中 reset-namesresets 属性顺序不匹配。

  • 驱动中的 ID 映射表与 DTS 不一致。

调试方法

  1. 查看 DTS 映射

    # 查看设备树复位属性
    dtc -I fs /sys/firmware/devicetree/base/ | grep resets
  2. 打印 ID 映射

    # 在驱动中添加调试打印
    dev_info(reset->dev, "Reset ID: %lu -> bank %d bit %d\n", id, bank, bit);

3.3 复位不生效(复位信号被锁定)

现象devm_reset_control_assert 后,外设仍处于工作状态,未复位。

原因

  • 复位控制器本身处于复位状态。

  • SoC 的复位逻辑被锁定(如写保护)。

  • 外设自带有独立复位逻辑。

调试方法

  1. 检查 CRU 写保护

    devmem2 <base>+0x0  # 读取 CRU 控制寄存器
  2. 手动解除写保护

    # 通常 CRU 有写保护密钥
    devmem2 <base>+0x0 0xDEADBEEF
  3. 使用逻辑分析仪:观察复位引脚的变化。


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

场景:系统启动后,I2C 控制器无法被内核驱动识别,i2cdetect -y 0 出现 No i2c device。DTS 中 I2C 配置正常。

分析流程

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

    • dmesg | grep i2c 显示 "Failed to probe"。

    • perf top 显示 rockchip_reset_reset 未被调用。

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

    devmem2 <reset_base>+<bank_offset>  # 读取复位寄存器

    发现 I2C 对应的复位位为 0 (复位状态)。

  3. I2C 驱动层(I2C 控制器层):

    cat /sys/kernel/debug/clk/clk_summary | grep i2c

    发现 I2C 时钟已使能。

  4. 根本原因

    • Bootloader 在初始化时,对 I2C 控制器执行了复位操作,但复位信号未被释放。

    • 内核的 I2C 驱动在 probe 中仅配置了时钟,未调用 reset_control_deassert

  5. 解决方案

    • 在 I2C 驱动的 probe 中添加 reset_control_deassert 调用。

    • 或者在 DTS 中为 I2C 添加 resets 属性,并确保驱动支持复位控制。

    • 手动强制释放复位:devmem2 <reset_base>+<bank_offset> 0xFFFFFFFF


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

控制器 协同方式 调试关键点
Clock Controller (CCU) Reset 控制器通常在 CRU 中与时钟一起管理 复位释放前必须确保时钟已使能
Power Domain 某些外设复位依赖于电源域 电源域上电后才能释放复位
PINCTRL 复位引脚配置 复位引脚方向、电压、上拉/下拉
Watchdog 系统复位由 Watchdog 触发 复位来源是 Watchdog、电源上电还是软件触发

第三部分 Mailbox 控制器 (Rockchip Mailbox)

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

Mailbox 是多核 SoC 中用于核间通信(Inter-Processor Communication, IPC)的关键硬件模块。它允许一个处理器核心向另一个核心发送消息或中断,实现异构多核(如 ARM + DSP / RISC-V / MCU)之间的协同工作。Mailbox 通常作为 Platform 设备挂载在 SoC 内部总线上。

在 Linux 5.10 中,Mailbox 子系统位于 drivers/mailbox/,核心层次分为三层:

  1. Mailbox 核心层 (drivers/mailbox/mailbox.c):提供通用的 Mailbox 控制器抽象。

  2. Mailbox 控制器驱动(本篇文章重点):具体的 SoC Mailbox 硬件驱动(如 rockchip-mailbox.c)。

  3. Mailbox 客户端驱动:使用 Mailbox 发送消息的驱动(如 rpmsgremoteproc)。

1.1 硬件关键概念

  • Mailbox 寄存器:消息寄存器、状态寄存器、控制寄存器、中断使能。

  • 通道:每个 Mailbox 包含多个独立的消息通道(通常 4 ~ 16 个)。

  • 消息:每次传输包含 32 位或 64 位数据。

  • 中断:接收端在收到消息时触发中断,发送端在消息发送完成时触发中断。

  • 轮询模式:支持无中断的轮询模式(用于低延迟场景)。

1.2 核心代码

// 基于 Linux 5.10 drivers/mailbox/rockchip-mailbox.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mailbox_controller.h>
#include <linux/mailbox_client.h>
​
/**
 * @brief Rockchip Mailbox 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Mailbox 硬件实例。
 */
struct rockchip_mailbox {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *pclk;               /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct mbox_controller mbox;    /**< Mailbox 控制器核心抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 num_chans;                  /**< 通道数量 */
    u32 chan_cfg[8];                /**< 每个通道的配置 */
    struct completion complete;     /**< 发送完成同步 */
};
​
/* 寄存器偏移量 (Rockchip Mailbox) */
#define MAILBOX_CTRL                0x00
#define MAILBOX_STATUS              0x04
#define MAILBOX_INT_EN              0x08
#define MAILBOX_INT_STAT            0x0C
#define MAILBOX_MSG_CHAN0           0x10
#define MAILBOX_MSG_CHAN1           0x14
#define MAILBOX_MSG_CHAN2           0x18
#define MAILBOX_MSG_CHAN3           0x1C
#define MAILBOX_RX_CONFIRM          0x20
#define MAILBOX_TX_CONFIRM          0x24
​
/**
 * @brief 初始化 Mailbox 硬件。
 * 
 * @param mbox 指向 rockchip_mailbox 结构。
 */
static void rockchip_mailbox_hw_init(struct rockchip_mailbox *mbox)
{
    u32 ctrl;
​
    // 1. 启用 Mailbox 控制器
    ctrl = (1 << 0) | (1 << 1);  // 启用核心和中断
    writel(ctrl, mbox->base + MAILBOX_CTRL);
​
    // 2. 配置通道中断 (启用所有通道)
    writel(0xFF, mbox->base + MAILBOX_INT_EN);
}
​
/**
 * @brief 发送 Mailbox 消息。
 * 
 * @param mbox 指向 rockchip_mailbox 结构。
 * @param ch 通道号。
 * @param msg 消息数据 (32 位)。
 * @return 0 成功。
 */
static int rockchip_mailbox_send_msg(struct rockchip_mailbox *mbox,
                                     int ch, u32 msg)
{
    unsigned long flags;
​
    if (ch >= mbox->num_chans) {
        dev_err(mbox->dev, "Invalid channel %d\n", ch);
        return -EINVAL;
    }
​
    // 1. 等待通道空闲
    while (readl(mbox->base + MAILBOX_STATUS) & (1 << ch))
        ;
​
    // 2. 写入消息到对应通道寄存器
    spin_lock_irqsave(&mbox->lock, flags);
    writel(msg, mbox->base + MAILBOX_MSG_CHAN0 + ch * 4);
    spin_unlock_irqrestore(&mbox->lock, flags);
​
    return 0;
}
​
/**
 * @brief 接收 Mailbox 消息。
 * 
 * @param mbox 指向 rockchip_mailbox 结构。
 * @param ch 通道号。
 * @return 32 位消息数据。
 */
static u32 rockchip_mailbox_recv_msg(struct rockchip_mailbox *mbox, int ch)
{
    u32 msg;
​
    if (ch >= mbox->num_chans) {
        dev_err(mbox->dev, "Invalid channel %d\n", ch);
        return 0;
    }
​
    // 1. 读取消息数据
    msg = readl(mbox->base + MAILBOX_MSG_CHAN0 + ch * 4);
​
    // 2. 清除接收状态 (确认收到)
    writel(1 << ch, mbox->base + MAILBOX_RX_CONFIRM);
​
    return msg;
}
​
/**
 * @brief Mailbox 发送操作 (客户端调用)。
 * 
 * @param chan 指向 mbox_chan 结构。
 * @param data 消息数据。
 * @return 0 成功。
 */
static int rockchip_mailbox_send_data(struct mbox_chan *chan, void *data)
{
    struct rockchip_mailbox *mbox = chan->con_priv;
    u32 msg = (u32)(uintptr_t)data;
​
    return rockchip_mailbox_send_msg(mbox, chan->idx, msg);
}
​
/**
 * @brief 启动 Mailbox 通道。
 * 
 * @param chan 指向 mbox_chan 结构。
 * @return 0 成功。
 */
static int rockchip_mailbox_startup(struct mbox_chan *chan)
{
    struct rockchip_mailbox *mbox = chan->con_priv;
​
    dev_info(mbox->dev, "Channel %d started\n", chan->idx);
    return 0;
}
​
/**
 * @brief 关闭 Mailbox 通道。
 * 
 * @param chan 指向 mbox_chan 结构。
 */
static void rockchip_mailbox_shutdown(struct mbox_chan *chan)
{
    struct rockchip_mailbox *mbox = chan->con_priv;
​
    dev_info(mbox->dev, "Channel %d shutdown\n", chan->idx);
}
​
/**
 * @brief Mailbox 中断处理函数。
 * 
 * 处理接收消息中断和发送完成中断。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_mailbox 结构。
 */
static irqreturn_t rockchip_mailbox_irq_handler(int irq, void *dev_id)
{
    struct rockchip_mailbox *mbox = dev_id;
    u32 int_stat;
    int i;
​
    // 1. 读取中断状态
    int_stat = readl(mbox->base + MAILBOX_INT_STAT);
​
    // 2. 处理接收消息中断 (RX)
    if (int_stat & 0x0F) {
        for (i = 0; i < 4; i++) {
            if (int_stat & (1 << i)) {
                // 接收数据
                u32 msg = rockchip_mailbox_recv_msg(mbox, i);
                // 通知客户端
                mbox_chan_received_data(&mbox->mbox.chans[i], &msg);
                // 清除中断
                writel(1 << i, mbox->base + MAILBOX_INT_STAT);
            }
        }
    }
​
    // 3. 处理发送完成中断 (TX)
    if (int_stat & 0xF0) {
        for (i = 0; i < 4; i++) {
            if (int_stat & (1 << (i + 4))) {
                // 通知客户端发送完成
                mbox_chan_txdone(&mbox->mbox.chans[i], 0);
                // 清除中断
                writel(1 << (i + 4), mbox->base + MAILBOX_INT_STAT);
            }
        }
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Mailbox 硬件,注册 Mailbox 控制器。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_mailbox_probe(struct platform_device *pdev)
{
    struct rockchip_mailbox *mbox;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL);
    if (!mbox)
        return -ENOMEM;
    platform_set_drvdata(pdev, mbox);
    mbox->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;
    }
    mbox->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(mbox->base))
        return PTR_ERR(mbox->base);
​
    // 3. 获取时钟
    mbox->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(mbox->pclk))
        return PTR_ERR(mbox->pclk);
    clk_prepare_enable(mbox->pclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    mbox->irq = irq;
​
    // 5. 配置 Mailbox 控制器
    mbox->num_chans = 4;  // 4 个通道
    mbox->mbox.dev = &pdev->dev;
    mbox->mbox.num_chans = mbox->num_chans;
    mbox->mbox.ops = &rockchip_mailbox_ops;
    mbox->mbox.chans = devm_kcalloc(&pdev->dev, mbox->num_chans,
                                    sizeof(struct mbox_chan), GFP_KERNEL);
    if (!mbox->mbox.chans) {
        ret = -ENOMEM;
        goto err_clk;
    }
​
    spin_lock_init(&mbox->lock);
    init_completion(&mbox->complete);
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, mbox->irq, rockchip_mailbox_irq_handler,
                           IRQF_SHARED, "rockchip-mailbox", mbox);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 7. 硬件初始化
    rockchip_mailbox_hw_init(mbox);
​
    // 8. 注册 Mailbox 控制器
    ret = devm_mbox_controller_register(&pdev->dev, &mbox->mbox);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register mailbox controller\n");
        goto err_clk;
    }
​
    dev_info(&pdev->dev, "Rockchip Mailbox controller registered at 0x%llx, IRQ %d, %d channels\n",
             (unsigned long long)res->start, mbox->irq, mbox->num_chans);
    return 0;
​
err_clk:
    clk_disable_unprepare(mbox->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_mailbox_remove(struct platform_device *pdev)
{
    struct rockchip_mailbox *mbox = platform_get_drvdata(pdev);
​
    // 1. 禁用 Mailbox
    writel(0, mbox->base + MAILBOX_CTRL);
​
    // 2. 禁用时钟
    clk_disable_unprepare(mbox->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_mailbox_of_match[] = {
    { .compatible = "rockchip,rk3399-mailbox" },
    { .compatible = "rockchip,rk3568-mailbox" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_mailbox_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_mailbox_driver = {
    .probe = rockchip_mailbox_probe,
    .remove = rockchip_mailbox_remove,
    .driver = {
        .name = "rockchip-mailbox",
        .of_match_table = rockchip_mailbox_of_match,
    },
};
module_platform_driver(rockchip_mailbox_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Mailbox Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Mailbox 调试核心难点

3.1 Mailbox 消息丢失

现象:发送端确认消息已发送,但接收端没有收到消息,或中断未触发。

原因

  • 通道被占用(发送端忙)。

  • 接收端中断被屏蔽。

  • 发送端和接收端的消息寄存器地址映射不同。

调试方法

  1. 检查通道状态

    devmem2 <base>+0x04  # MAILBOX_STATUS
  2. 检查中断状态

    devmem2 <base>+0x0C  # MAILBOX_INT_STAT
  3. 使用轮询模式测试:在驱动中临时切换轮询模式,验证中断问题。

3.2 中断风暴

现象:Mailbox 中断频繁触发,CPU 占用率高。

原因

  • 接收端未及时清除 RX 状态。

  • 发送端反复发送相同消息。

  • 硬件环路测试模式被意外启用。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep mailbox
  2. 检查 RX 确认

    devmem2 <base>+0x20  # MAILBOX_RX_CONFIRM

3.3 通道分配冲突

现象:多个客户端使用同一 Mailbox 通道,导致消息冲突。

原因

  • DTS 中未正确指定 mbox-names

  • 客户端驱动未正确处理通道分配。

调试方法

  1. 检查 DTS 配置

    dtc -I fs /sys/firmware/devicetree/base/ | grep mbox
  2. 跟踪客户端使用

    trace-cmd record -e mailbox:* -a -- timeout 5
    trace-cmd report | grep channel

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

场景:Cortex-A 核心与 Cortex-M 核心通过 Mailbox 通信,但偶尔出现消息滞后的现象。

分析流程

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

    • perf top 显示 rockchip_mailbox_irq_handler 占用 CPU 约 15%。

    • 中断频率约 1000 次/秒。

  2. Mailbox 层(Device Drivers -> Mailbox 层):

    devmem2 <base>+0x0C  # MAILBOX_INT_STAT

    发现通道 0 的 RX 中断频繁触发。

  3. 客户端驱动(RPMsg 层):

    cat /sys/kernel/debug/rpmsg/rpmsg0/status

    显示队列长度接近上限。

  4. 根本原因

    • Mailbox 通道的缓冲区过小(仅 32 位),无法存储多帧数据。

    • 客户端驱动未及时处理消息,导致接收端中断被阻塞。

  5. 解决方案

    • 增加客户端驱动的接收队列深度。

    • 启用 Mailbox 中断聚合,减少触发频率。

    • 使用 DMA 传输。


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

控制器 协同方式 调试关键点
Interrupt Controller 提供 Mailbox 中断路由 中断亲和性、优先级
Clock 提供 Mailbox 时钟 时钟频率匹配
Power Domain 确保 Mailbox 电源域稳定 上电顺序、复位状态
Remote Processor 通过 Mailbox 通信 RPMsg 消息协议、缓冲区管理

第四部分 GIC 中断控制器

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

GIC(Generic Interrupt Controller)是 ARM 架构 SoC 中最核心的系统组件之一。它负责接收来自所有外设(UART、I2C、SPI、DMA、GPU 等)的中断信号,进行优先级排序,然后路由到相应的 CPU 核心。GIC 通常作为 Platform 设备挂载在 SoC 内部总线上,是所有其他控制器驱动的“上级”依赖。

在 Linux 5.10 中,GIC 驱动位于 drivers/irqchip/,核心层次分为三层:

  1. IRQ 核心层 (kernel/irq/):提供中断号分配、中断处理、中断线程化等通用 API。

  2. GIC 控制器驱动(本篇文章重点):具体的 GIC 硬件驱动(如 irq-gic.cirq-gic-v3.c)。

  3. 中断客户端驱动:使用 request_irqdevm_request_irq 注册中断处理函数的驱动。

1.1 硬件关键概念

  • GIC 架构:GICv2(支持 8 个 CPU 接口)或 GICv3(支持更多 CPU 和独立中断组)。

  • GIC 组件

    • Distributor:接收中断,进行优先级排序和分发。

    • CPU Interface:每个 CPU 核心对应一个,负责向 CPU 传递中断。

    • Redistributor(GICv3 新增):支持独立的中断重分发。

  • 中断类型

    • PPI(Private Peripheral Interrupt):私有外设中断,每个 CPU 独立。

    • SPI(Shared Peripheral Interrupt):共享外设中断,所有 CPU 共享。

    • SGI(Software Generated Interrupt):软件生成中断。

  • 中断优先级:支持 16 级(GICv2)或 256 级(GICv3)优先级。

  • 中断亲和性:可以将中断路由到指定 CPU 核心。

1.2 核心代码

// 基于 Linux 5.10 drivers/irqchip/irq-gic.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
​
/**
 * @brief GIC 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 GIC 硬件实例。
 */
struct gic_chip_data {
    void __iomem *dist_base;       /**< Distributor 寄存器基址 */
    void __iomem *cpu_base;        /**< CPU Interface 寄存器基址 */
    struct irq_domain *domain;     /**< IRQ 域抽象 */
    struct irq_chip irq_chip;      /**< IRQ 芯片抽象 */
    u32 nr_irqs;                   /**< 支持的中断总数 */
    u32 ppi_nr_irqs;               /**< PPI 中断数量 */
    u32 spi_nr_irqs;               /**< SPI 中断数量 */
    spinlock_t lock;               /**< 硬件保护锁 */
};
​
/* 寄存器偏移量 (GICv2) */
#define GIC_DIST_CTRL              0x000
#define GIC_DIST_CTYPE             0x004
#define GIC_DIST_ENABLE_SET        0x100
#define GIC_DIST_ENABLE_CLEAR      0x180
#define GIC_DIST_PENDING_SET       0x200
#define GIC_DIST_PENDING_CLEAR     0x280
#define GIC_DIST_ACTIVE_SET        0x300
#define GIC_DIST_ACTIVE_CLEAR      0x380
#define GIC_DIST_PRI               0x400
#define GIC_DIST_TARGET            0x800
#define GIC_DIST_CONFIG            0xC00
#define GIC_DIST_SOFT_INT          0xF00
#define GIC_DIST_SGI_CTLR          0xF10
​
#define GIC_CPU_CTRL               0x00
#define GIC_CPU_PRIORITY_MASK      0x04
#define GIC_CPU_BINPOINT           0x08
#define GIC_CPU_INT_ACK            0x0C
#define GIC_CPU_EOI                0x10
#define GIC_CPU_RUNNING_PRI        0x14
#define GIC_CPU_HIGHPRI_PENDING    0x18
​
/**
 * @brief 初始化 GIC Distributor。
 * 
 * @param gic 指向 gic_chip_data 结构。
 */
static void gic_dist_init(struct gic_chip_data *gic)
{
    u32 ctrl;
    int i;
​
    // 1. 读取 GIC 配置信息
    gic->nr_irqs = readl(gic->dist_base + GIC_DIST_CTYPE) & 0x1F;
    gic->nr_irqs = (gic->nr_irqs + 1) * 32;
​
    // 2. 禁用所有中断
    for (i = 0; i < gic->nr_irqs / 32; i++) {
        writel(0xFFFFFFFF, gic->dist_base + GIC_DIST_ENABLE_CLEAR + i * 4);
    }
​
    // 3. 配置中断触发方式 (默认边缘触发)
    for (i = 0; i < gic->nr_irqs / 32; i++) {
        writel(0x00000000, gic->dist_base + GIC_DIST_CONFIG + i * 4);
    }
​
    // 4. 设置中断优先级 (默认最低)
    for (i = 0; i < gic->nr_irqs / 4; i++) {
        writel(0xA0A0A0A0, gic->dist_base + GIC_DIST_PRI + i * 4);
    }
​
    // 5. 设置中断亲和性 (路由到 CPU0)
    for (i = 0; i < gic->nr_irqs / 4; i++) {
        writel(0x01010101, gic->dist_base + GIC_DIST_TARGET + i * 4);
    }
​
    // 6. 启用 Distributor
    ctrl = readl(gic->dist_base + GIC_DIST_CTRL);
    ctrl |= 0x1;  // 启用 GIC
    writel(ctrl, gic->dist_base + GIC_DIST_CTRL);
}
​
/**
 * @brief 初始化 GIC CPU Interface。
 * 
 * @param gic 指向 gic_chip_data 结构。
 */
static void gic_cpu_init(struct gic_chip_data *gic)
{
    u32 ctrl;
​
    // 1. 设置优先级掩码 (允许所有优先级)
    writel(0xF0, gic->cpu_base + GIC_CPU_PRIORITY_MASK);
​
    // 2. 启用 CPU Interface
    ctrl = readl(gic->cpu_base + GIC_CPU_CTRL);
    ctrl |= 0x1;  // 启用
    writel(ctrl, gic->cpu_base + GIC_CPU_CTRL);
}
​
/**
 * @brief 启用一个中断 (IRQ 芯片回调)。
 * 
 * @param data 指向 irq_data。
 */
static void gic_irq_enable(struct irq_data *data)
{
    struct gic_chip_data *gic = irq_data_get_irq_chip_data(data);
    u32 hwirq = data->hwirq;
    u32 bank = hwirq / 32;
    u32 bit = hwirq % 32;
​
    writel(1 << bit, gic->dist_base + GIC_DIST_ENABLE_SET + bank * 4);
}
​
/**
 * @brief 禁用一个中断 (IRQ 芯片回调)。
 * 
 * @param data 指向 irq_data。
 */
static void gic_irq_disable(struct irq_data *data)
{
    struct gic_chip_data *gic = irq_data_get_irq_chip_data(data);
    u32 hwirq = data->hwirq;
    u32 bank = hwirq / 32;
    u32 bit = hwirq % 32;
​
    writel(1 << bit, gic->dist_base + GIC_DIST_ENABLE_CLEAR + bank * 4);
}
​
/**
 * @brief 读取中断号 (IRQ 芯片回调)。
 * 
 * @param data 指向 irq_data。
 * @param irq 指向 irq。
 * @param regs 指向 pt_regs。
 * @return 无。
 */
static void gic_irq_ack(struct irq_data *data, struct pt_regs *regs)
{
    // GIC 通过 CPU Interface 的 ACK 寄存器读取中断号
    // 此操作由核心代码在 irq 处理流程中处理
}
​
/**
 * @brief 中断完成 (IRQ 芯片回调)。
 * 
 * @param data 指向 irq_data。
 */
static void gic_irq_eoi(struct irq_data *data)
{
    struct gic_chip_data *gic = irq_data_get_irq_chip_data(data);
    u32 hwirq = data->hwirq;
​
    // 写入 EOI 寄存器通知中断完成
    writel(hwirq, gic->cpu_base + GIC_CPU_EOI);
}
​
/**
 * @brief 设置中断触发方式 (IRQ 芯片回调)。
 * 
 * @param data 指向 irq_data。
 * @param type 触发类型 (上升沿/下降沿/高电平/低电平)。
 * @return 0 成功。
 */
static int gic_irq_set_type(struct irq_data *data, unsigned int type)
{
    struct gic_chip_data *gic = irq_data_get_irq_chip_data(data);
    u32 hwirq = data->hwirq;
    u32 bank = hwirq / 32;
    u32 bit = hwirq % 32;
    u32 config = 0;
​
    switch (type) {
    case IRQ_TYPE_EDGE_RISING:
        config = 0x2;  // 上升沿触发
        break;
    case IRQ_TYPE_EDGE_FALLING:
        config = 0x3;  // 下降沿触发
        break;
    case IRQ_TYPE_LEVEL_HIGH:
        config = 0x1;  // 高电平触发
        break;
    case IRQ_TYPE_LEVEL_LOW:
        config = 0x0;  // 低电平触发
        break;
    default:
        return -EINVAL;
    }
​
    // 配置触发方式
    u32 val = readl(gic->dist_base + GIC_DIST_CONFIG + bank * 4);
    val &= ~(0x3 << (bit * 2));
    val |= (config << (bit * 2));
    writel(val, gic->dist_base + GIC_DIST_CONFIG + bank * 4);
​
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 GIC 硬件,注册 IRQ 域。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int gic_probe(struct platform_device *pdev)
{
    struct gic_chip_data *gic;
    struct resource *res;
    int ret;
​
    // 1. 分配私有数据结构
    gic = devm_kzalloc(&pdev->dev, sizeof(*gic), GFP_KERNEL);
    if (!gic)
        return -ENOMEM;
    platform_set_drvdata(pdev, gic);
​
    // 2. 获取 Distributor 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No distributor resource\n");
        return -ENXIO;
    }
    gic->dist_base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(gic->dist_base))
        return PTR_ERR(gic->dist_base);
​
    // 3. 获取 CPU Interface 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (!res) {
        dev_err(&pdev->dev, "No CPU interface resource\n");
        return -ENXIO;
    }
    gic->cpu_base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(gic->cpu_base))
        return PTR_ERR(gic->cpu_base);
​
    spin_lock_init(&gic->lock);
​
    // 4. 初始化硬件
    gic_dist_init(gic);
    gic_cpu_init(gic);
​
    // 5. 创建 IRQ 域
    gic->domain = irq_domain_add_linear(pdev->dev.of_node, gic->nr_irqs,
                                        &gic_irq_domain_ops, gic);
    if (!gic->domain) {
        dev_err(&pdev->dev, "Failed to add IRQ domain\n");
        return -ENOMEM;
    }
​
    // 6. 设置 IRQ 芯片操作
    gic->irq_chip.name = "GIC";
    gic->irq_chip.irq_enable = gic_irq_enable;
    gic->irq_chip.irq_disable = gic_irq_disable;
    gic->irq_chip.irq_eoi = gic_irq_eoi;
    gic->irq_chip.irq_set_type = gic_irq_set_type;
​
    dev_info(&pdev->dev, "GIC controller registered at %pR, %d interrupts\n",
             res, gic->nr_irqs);
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id gic_of_match[] = {
    { .compatible = "arm,gic-400" },
    { .compatible = "arm,gic-v2" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gic_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver gic_driver = {
    .probe = gic_probe,
    .remove = gic_remove,
    .driver = {
        .name = "gic",
        .of_match_table = gic_of_match,
    },
};
module_platform_driver(gic_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ARM GIC Interrupt Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 GIC 调试核心难点

3.1 中断未触发

现象:外设中断未调用 ISR,cat /proc/interrupts 未计数。

原因

  • GIC 未正确配置中断触发方式。

  • 中断在 Distributor 中未启用。

  • 中断号映射错误。

调试方法

  1. 检查 GIC 状态

    devmem2 <dist_base>+0x04  # GIC_DIST_CTYPE
  2. 检查中断使能

    devmem2 <dist_base>+0x100  # GIC_DIST_ENABLE_SET
  3. 检查触发方式

    devmem2 <dist_base>+0xC00  # GIC_DIST_CONFIG
  4. 手动触发中断

    # 软件生成中断
    devmem2 <dist_base>+0xF00 0x01

3.2 中断风暴

现象:特定中断号触发频繁,CPU 占用高,中断计数异常。

原因

  • 外设中断未清除状态。

  • 中断配置为边缘触发但电平信号持续。

  • 中断优先级被设置为最高。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep <irq_num>
  2. 临时屏蔽中断

    echo <irq_num> > /proc/irq/<irq_num>/disable
  3. 检查外设寄存器:确认中断状态已清除。

3.3 中断亲和性配置失败

现象:中断无法路由到指定 CPU 核心。

原因

  • GIC 版本不支持中断亲和性。

  • 中断类型为 PPI(私有中断,不可路由)。

  • 亲和性配置被内核屏蔽。

调试方法

  1. 检查 GIC 版本

    cat /sys/kernel/debug/irq/irq_domain_mapping
  2. 检查中断类型

    cat /proc/interrupts | grep "PPI"
  3. 强制设置亲和性

    echo 2 > /proc/irq/<irq_num>/smp_affinity

3.4 中断延迟过高

现象:中断响应延迟高,perf 显示中断处理延迟。

原因

  • 中断优先级被屏蔽。

  • 高优先级中断抢占。

  • 中断被调度到繁忙的 CPU 核心。

调试方法

  1. 测量中断延迟

    cyclictest -p 99 -t 5 -n
  2. 检查中断优先级

    devmem2 <dist_base>+0x400  # GIC_DIST_PRI

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

控制器 协同方式 调试关键点
所有外设控制器 外设中断通过 GIC 路由到 CPU 中断号映射、触发方式
CPU GIC 向 CPU 发送中断信号 中断优先级、亲和性
PMU GIC 支持 PMU 中断 性能监控中断
PIT GIC 支持定时器中断 定时器中断处理

第五部分 IOMMU/SMMU 控制器 (ARM SMMU)

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

SMMU (System Memory Management Unit) 是 ARM 架构 SoC 中用于管理设备访问内存的硬件模块。它类似于 CPU 的 MMU,但专门服务于外设(如 GPU、NPU、DMA、显示器控制器等),为外设提供独立的地址空间映射、访问权限控制和虚拟地址支持。

在 Linux 5.10 中,SMMU 驱动位于 drivers/iommu/,核心层次分为三层:

  1. IOMMU 核心层 (drivers/iommu/iommu.c):提供通用的 IOMMU 抽象 API。

  2. SMMU 控制器驱动(本篇文章重点):具体的 SMMU 硬件驱动(如 arm-smmu.c)。

  3. IOMMU 客户端驱动:使用 iommu_map/unmap 的外设驱动(如 panfrostdrm)。

1.1 硬件关键概念

  • SMMU 架构:支持 Stream ID (SID) 识别设备、Context Bank 管理页表。

  • 页表:支持多级页表(通常 2~3 级,每级 64KB 或 2MB)。

  • Stream ID:每个设备有唯一的 SID,用于绑定页表。

  • Context Bank:每个设备可以分配独立的页表上下文。

  • TLB:硬件缓存,加速地址转换。

  • Fault 处理:支持缺页异常和访问错误报告。

  • 中断:处理缺页、访问错误等事件。

1.2 核心代码

// 基于 Linux 5.10 drivers/iommu/arm-smmu.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/iommu.h>
#include <linux/slab.h>
​
/**
 * @brief ARM SMMU 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 SMMU 硬件实例。
 */
struct arm_smmu_device {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *clk;                /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct iommu_device iommu;      /**< IOMMU 核心抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 version;                    /**< SMMU 版本号 */
    u32 num_context_banks;          /**< Context Bank 数量 */
    u32 num_global_cmd;             /**< 全局命令寄存器数量 */
    u32 stream_id_mask;             /**< Stream ID 位掩码 */
    struct arm_smmu_cb *cbs;        /**< Context Bank 数组 */
    struct device_node *smmu_node;  /**< 设备树节点 */
};
​
/* 寄存器偏移量 (ARM SMMU v2) */
#define SMMU_CTRL                   0x00
#define SMMU_STATUS                 0x04
#define SMMU_CR0                    0x08
#define SMMU_CR1                    0x0C
#define SMMU_CR2                    0x10
#define SMMU_CBA2R                  0x200
#define SMMU_STRTAB                 0x400
#define SMMU_STRTAB_BASE            0x404
#define SMMU_STRTAB_BASE_CFG        0x408
#define SMMU_CB_BASE                0x800  // Context Bank 起始地址
​
/**
 * @brief 初始化 SMMU 硬件。
 * 
 * @param smmu 指向 arm_smmu_device 结构。
 * @return 0 成功。
 */
static int arm_smmu_hw_init(struct arm_smmu_device *smmu)
{
    u32 ctrl;
​
    // 1. 检查 SMMU 版本
    smmu->version = readl(smmu->base + SMMU_CR0);
    if (smmu->version < 0x20) {
        dev_err(smmu->dev, "Unsupported SMMU version 0x%x\n", smmu->version);
        return -EINVAL;
    }
​
    // 2. 禁用 SMMU 进行配置
    writel(0x0, smmu->base + SMMU_CTRL);
    while (readl(smmu->base + SMMU_STATUS) & 0x1)
        ;
​
    // 3. 配置 CR1 (分页大小、地址空间)
    u32 cr1 = (1 << 0) | (1 << 1) | (1 << 2);  // 启用 4KB、2MB、1GB 页
    writel(cr1, smmu->base + SMMU_CR1);
​
    // 4. 设置 Stream ID 掩码 (0xFFFF)
    writel(0xFFFF, smmu->base + SMMU_CR2);
​
    // 5. 启用 SMMU
    ctrl = (1 << 0);  // 全局启用
    writel(ctrl, smmu->base + SMMU_CTRL);
    while (!(readl(smmu->base + SMMU_STATUS) & 0x1))
        ;
​
    return 0;
}
​
/**
 * @brief 初始化 Context Bank。
 * 
 * @param smmu 指向 arm_smmu_device 结构。
 * @param sid Stream ID。
 * @param pgd 页表基地址。
 */
static void arm_smmu_init_context_bank(struct arm_smmu_device *smmu,
                                       u32 sid, dma_addr_t pgd)
{
    void __iomem *cb_base = smmu->base + SMMU_CB_BASE;
    u32 cbar, s1cr;
​
    // 1. 分配 Context Bank (假设固定分配)
    int cb_idx = sid % smmu->num_context_banks;
​
    // 2. 设置 Context Bank 的页表地址
    writel(pgd, cb_base + cb_idx * 0x80 + 0x18);  // TTBR0
​
    // 3. 设置 Stream ID 映射
    cbar = readl(smmu->base + SMMU_CBA2R + cb_idx * 4);
    cbar &= ~0xFFFF;
    cbar |= sid;
    writel(cbar, smmu->base + SMMU_CBA2R + cb_idx * 4);
​
    // 4. 启用 Context Bank
    s1cr = (1 << 0) | (1 << 1);  // 启用 S1 翻译,启用 IPA
    writel(s1cr, cb_base + cb_idx * 0x80 + 0x00);
}
​
/**
 * @brief 映射设备地址到 IOMMU 地址。
 * 
 * @param domain 指向 iommu_domain。
 * @param iova 虚拟地址。
 * @param paddr 物理地址。
 * @param size 映射大小。
 * @param prot 访问权限。
 * @return 0 成功。
 */
static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
                        phys_addr_t paddr, size_t size, int prot)
{
    struct arm_smmu_device *smmu = to_arm_smmu(domain);
    struct arm_smmu_domain *smmu_domain = domain->priv;
    dma_addr_t pgd = smmu_domain->pgd;
    int ret;
​
    // 1. 检查对齐要求
    if (size < PAGE_SIZE || (iova & (PAGE_SIZE - 1)) ||
        (paddr & (PAGE_SIZE - 1))) {
        dev_err(smmu->dev, "Invalid mapping alignment\n");
        return -EINVAL;
    }
​
    // 2. 遍历页表 (此处简化,假设直接映射)
    // 实际实现需要遍历多级页表,分配页表项
    // 这里假设使用 2MB 大页
    size_t nr_pages = size >> PAGE_SHIFT;
    for (int i = 0; i < nr_pages; i++) {
        u32 pte = (paddr + i * PAGE_SIZE) >> PAGE_SHIFT;
        // 写入页表项
        // ...
    }
​
    return 0;
}
​
/**
 * @brief 解除设备地址到 IOMMU 地址的映射。
 * 
 * @param domain 指向 iommu_domain。
 * @param iova 虚拟地址。
 * @param size 映射大小。
 * @return 0 成功。
 */
static void arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
                           size_t size)
{
    struct arm_smmu_device *smmu = to_arm_smmu(domain);
​
    // 1. 清空页表项
    // ...
}
​
/**
 * @brief 分配 IOMMU 域。
 * 
 * @param type 域类型 (DMA/IDENTITY/UNMANAGED)。
 * @param fmt 格式 (32/64 位地址)。
 * @param ops IOMMU 操作回调。
 * @return 指向 iommu_domain。
 */
static struct iommu_domain *arm_smmu_domain_alloc(unsigned int type)
{
    struct arm_smmu_domain *smmu_domain;
​
    smmu_domain = kzalloc(sizeof(*smmu_domain), GFP_KERNEL);
    if (!smmu_domain)
        return NULL;
​
    smmu_domain->domain.ops = &arm_smmu_ops;
    // 分配页表
    smmu_domain->pgd = dma_alloc_coherent(NULL, PAGE_SIZE, NULL, GFP_KERNEL);
    if (!smmu_domain->pgd) {
        kfree(smmu_domain);
        return NULL;
    }
​
    return &smmu_domain->domain;
}
​
/**
 * @brief 释放 IOMMU 域。
 * 
 * @param domain 指向 iommu_domain。
 */
static void arm_smmu_domain_free(struct iommu_domain *domain)
{
    struct arm_smmu_domain *smmu_domain = domain->priv;
​
    dma_free_coherent(NULL, PAGE_SIZE, smmu_domain->pgd, smmu_domain->pgd);
    kfree(smmu_domain);
}
​
/**
 * @brief SMMU 中断处理函数。
 * 
 * 处理缺页异常和访问错误。
 *
 * @param irq 中断号。
 * @param dev_id 指向 arm_smmu_device 结构。
 */
static irqreturn_t arm_smmu_irq_handler(int irq, void *dev_id)
{
    struct arm_smmu_device *smmu = dev_id;
    u32 fault_status;
​
    // 1. 读取错误状态
    fault_status = readl(smmu->base + SMMU_STATUS);
​
    // 2. 处理缺页中断
    if (fault_status & (1 << 0)) {
        dev_err(smmu->dev, "SMMU page fault detected\n");
        // 读取 fault 地址
        u32 fault_addr = readl(smmu->base + 0x100);
        dev_err(smmu->dev, "Fault address: 0x%08x\n", fault_addr);
        // 清除错误
        writel(1 << 0, smmu->base + SMMU_STATUS);
    }
​
    // 3. 处理访问权限错误
    if (fault_status & (1 << 1)) {
        dev_err(smmu->dev, "SMMU permission fault\n");
        writel(1 << 1, smmu->base + SMMU_STATUS);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 SMMU 硬件,注册 IOMMU 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int arm_smmu_probe(struct platform_device *pdev)
{
    struct arm_smmu_device *smmu;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    smmu = devm_kzalloc(&pdev->dev, sizeof(*smmu), GFP_KERNEL);
    if (!smmu)
        return -ENOMEM;
    platform_set_drvdata(pdev, smmu);
    smmu->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;
    }
    smmu->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(smmu->base))
        return PTR_ERR(smmu->base);
​
    // 3. 获取时钟
    smmu->clk = devm_clk_get(&pdev->dev, "clk");
    if (IS_ERR(smmu->clk))
        return PTR_ERR(smmu->clk);
    clk_prepare_enable(smmu->clk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    smmu->irq = irq;
​
    // 5. 读取 SMMU 配置
    smmu->num_context_banks = 64;
    smmu->num_global_cmd = 4;
    smmu->stream_id_mask = 0xFFFF;
    smmu->cbs = devm_kcalloc(&pdev->dev, smmu->num_context_banks,
                             sizeof(struct arm_smmu_cb), GFP_KERNEL);
    if (!smmu->cbs) {
        ret = -ENOMEM;
        goto err_clk;
    }
​
    spin_lock_init(&smmu->lock);
​
    // 6. 硬件初始化
    ret = arm_smmu_hw_init(smmu);
    if (ret) {
        dev_err(&pdev->dev, "Failed to initialize hardware\n");
        goto err_clk;
    }
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, smmu->irq, arm_smmu_irq_handler,
                           IRQF_SHARED, "arm-smmu", smmu);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 8. 注册 IOMMU 设备
    ret = iommu_device_sysfs_add(&smmu->iommu, smmu->dev, NULL, "smmu");
    if (ret) {
        dev_err(&pdev->dev, "Failed to add iommu device\n");
        goto err_clk;
    }
    iommu_device_register(&smmu->iommu, &arm_smmu_ops, smmu);
​
    // 9. 注册到 IOMMU 核心
    bus_set_iommu(&pci_bus_type, &arm_smmu_ops);
    bus_set_iommu(&platform_bus_type, &arm_smmu_ops);
​
    dev_info(&pdev->dev, "ARM SMMU controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, smmu->irq);
    return 0;
​
err_clk:
    clk_disable_unprepare(smmu->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int arm_smmu_remove(struct platform_device *pdev)
{
    struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
​
    // 1. 禁用 SMMU
    writel(0x0, smmu->base + SMMU_CTRL);
​
    // 2. 注销 IOMMU 设备
    iommu_device_unregister(&smmu->iommu);
    iommu_device_sysfs_remove(&smmu->iommu);
​
    // 3. 禁用时钟
    clk_disable_unprepare(smmu->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id arm_smmu_of_match[] = {
    { .compatible = "arm,smmu-v2" },
    { .compatible = "arm,smmu-v3" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver arm_smmu_driver = {
    .probe = arm_smmu_probe,
    .remove = arm_smmu_remove,
    .driver = {
        .name = "arm-smmu",
        .of_match_table = arm_smmu_of_match,
    },
};
module_platform_driver(arm_smmu_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ARM SMMU IOMMU Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 SMMU 调试核心难点

3.1 设备无法通过 SMMU 访问内存

现象:外设驱动加载成功,但 DMA 访问失败,dmesg 显示 "SMMU page fault"。

原因

  • Stream ID 未正确配置。

  • 页表映射不完整。

  • 设备未绑定到 SMMU 域。

调试方法

  1. 检查 SMMU 错误状态

    devmem2 <base>+0x04  # SMMU_STATUS
  2. 读取 fault 地址

    devmem2 <base>+0x100  # 错误地址寄存器
  3. 检查 Stream ID

    # 从设备树检查 stream-id
    dtc -I fs /sys/firmware/devicetree/base/ | grep iommus
  4. 强制绑定到 SMMU

    echo "1" > /sys/kernel/debug/iommu/arm-smmu/streamid

3.2 性能下降(TLB 未命中率高)

现象:使用 SMMU 后,外设吞吐量下降,perf 显示大量 TLB 未命中。

原因

  • 页表层级过多。

  • 映射粒度太小(频繁 4KB 映射)。

  • TLB 缓存大小不足。

调试方法

  1. 查看 TLB 统计

    cat /sys/kernel/debug/iommu/arm-smmu/tlb_stats
  2. 增大映射粒度:使用 2MB 大页映射。

  3. 调整页表层级:减少页表层级(如从 3 级降为 2 级)。

3.3 中断风暴 (SMMU 错误频繁触发)

现象:SMMU 错误中断频繁触发,CPU 占用高。

原因

  • 设备未正确初始化。

  • 内存访问越界。

  • 页表映射错误。

调试方法

  1. 屏蔽错误中断

    # 临时屏蔽错误中断
    devmem2 <base>+0x08 0x00
  2. 逐步启用设备:依次绑定设备,定位哪个设备触发错误。

  3. 检查映射一致性:确保 iommu_map/unmap 成对调用。

3.4 多设备冲突

现象:多个设备共享 SMMU,导致地址冲突或映射错误。

原因

  • Stream ID 重复。

  • 地址空间重叠。

  • Context Bank 分配冲突。

调试方法

  1. 检查 Stream ID 分配

    cat /sys/kernel/debug/iommu/arm-smmu/streamid_list
  2. 分配独立的 Context Bank

    # 强制设备使用独立的 Context Bank
    echo <dev_name> > /sys/kernel/debug/iommu/arm-smmu/independent_cb

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

场景:GPU 使用 SMMU 后,帧率从 60fps 下降到 30fps,perf 显示大量 TLB 未命中。

分析流程

  1. 宏观层面(Memory 层):

    • perf top 显示 arm_smmu_maparm_smmu_unmap 占用 CPU 约 30%。

    • 内存带宽正常。

  2. SMMU 层(Device Drivers -> SMMU 层):

    cat /sys/kernel/debug/iommu/arm-smmu/tlb_stats

    发现 TLB 未命中率高达 80%。

  3. GPU 层(GPU 控制器层):

    cat /sys/kernel/debug/dri/0/panfrost/mm_fault

    显示大量 4KB 粒度映射。

  4. 根本原因

    • GPU 纹理数据被分割成大量 4KB 的小映射。

    • SMMU 频繁进行页表遍历,TLB 缓存未命中。

    • 映射粒度过小,TLB 无法覆盖足够大的地址范围。

  5. 解决方案

    • 调整 GPU 驱动使用 2MB 大页映射。

    • 增大 TLB 缓存大小(如果硬件支持)。

    • 使用 madvise 管理内存分配。

    • 优化后,帧率恢复到 55fps。


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

控制器 协同方式 调试关键点
GPU 通过 SMMU 访问纹理内存 页表粒度、TLB 未命中
NPU 通过 SMMU 访问模型权重 Stream ID、地址映射
DMA 通过 SMMU 访问 DMA 缓冲区 地址连续、对齐
Memory Controller SMMU 访问物理内存 内存带宽、争用
IOMMU Core 管理 SMMU 抽象 域绑定、映射一致性

第六部分 Power Domain 控制器 (Rockchip PMU)

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

Power Domain (电源域) 控制器是 SoC 电源管理架构的核心组件,负责管理 SoC 内部各个硬件模块(如 GPU、NPU、ISP、VPU、DDR、USB、PCIe 等)的独立上电与断电。它通过控制电源开关和隔离逻辑,在系统不需要某个模块时彻底关闭其供电,以达到显著的功耗优化目的。

在 Linux 5.10 中,Power Domain 控制通过 Generic Power Domain (GenPD) 框架实现,drivers/soc/rockchip/ 下提供了具体的 Rockchip PMU 驱动。

1.1 硬件关键概念

  • PMU (Power Management Unit):SoC 内部的电源管理控制器,包含寄存器用于控制各域的上电/下电和状态查询。

  • 电源域寄存器:包含 PWR_ON 位、PWR_ST (状态) 位以及 ACK (确认) 位。

  • 上电/下电流程:写寄存器 -> 等待状态变化 -> 等待硬件确认。

  • 依赖关系:某些域需要其他域先上电才能工作(依赖链)。

  • 时钟与复位:通常电源域的上电/下电与相关模块的时钟和复位信号联动。

1.2 核心代码

// 基于 Linux 5.10 drivers/soc/rockchip/rockchip-pm-domains.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/regmap.h>
​
/**
 * @brief 描述一个 Rockchip 电源域。
 */
struct rockchip_pm_domain {
    struct generic_pm_domain genpd;  /**< 通用电源域抽象 */
    u32 pwr_reg;                     /**< 电源控制寄存器偏移 */
    u32 pwr_st_reg;                  /**< 状态寄存器偏移 */
    u32 pwr_mask;                    /**< 电源控制位掩码 */
    u32 pwr_st_mask;                 /**< 状态位掩码 */
    u32 ack_reg;                     /**< 确认寄存器偏移 */
    u32 ack_bit;                     /**< 确认位索引 */
    int req_clk_cnt;                 /**< 需求时钟数量 */
    struct clk_bulk_data *clks;      /**< 时钟列表 */
};
​
/**
 * @brief Rockchip PMU 控制器私有数据结构。
 */
struct rockchip_pmu_info {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    struct device *dev;              /**< 设备指针 */
    struct genpd_onecell_data pd_data; /**< 电源域单元格数据 */
    struct rockchip_pm_domain *domains; /**< 电源域数组 */
    int num_domains;                 /**< 电源域数量 */
    spinlock_t lock;                 /**< 硬件保护锁 */
};
​
/**
 * @brief 上电一个电源域。
 *
 * @param genpd 指向 generic_pm_domain。
 * @return 0 成功。
 */
static int rockchip_pd_power_on(struct generic_pm_domain *genpd)
{
    struct rockchip_pm_domain *pd = container_of(genpd, struct rockchip_pm_domain, genpd);
    struct rockchip_pmu_info *pmu = genpd->dev->devdata;
    unsigned long flags;
    u32 val;
    int ret;
​
    spin_lock_irqsave(&pmu->lock, flags);
​
    // 1. 使能相关时钟
    ret = clk_bulk_prepare_enable(pd->req_clk_cnt, pd->clks);
    if (ret) {
        dev_err(pmu->dev, "Failed to enable clocks for domain %s\n", genpd->name);
        spin_unlock_irqrestore(&pmu->lock, flags);
        return ret;
    }
​
    // 2. 设置电源控制位 (写 1 表示上电)
    val = readl_relaxed(pmu->base + pd->pwr_reg);
    val |= pd->pwr_mask;
    writel_relaxed(val, pmu->base + pd->pwr_reg);
​
    // 3. 等待状态位切换
    int timeout = 2000;
    while (timeout--) {
        val = readl_relaxed(pmu->base + pd->pwr_st_reg);
        if ((val & pd->pwr_st_mask) == pd->pwr_st_mask)
            break;
        udelay(1);
    }
    if (timeout <= 0) {
        dev_err(pmu->dev, "Domain %s power on timeout\n", genpd->name);
        clk_bulk_disable_unprepare(pd->req_clk_cnt, pd->clks);
        spin_unlock_irqrestore(&pmu->lock, flags);
        return -ETIMEDOUT;
    }
​
    // 4. 等待硬件确认
    timeout = 2000;
    while (timeout--) {
        val = readl_relaxed(pmu->base + pd->ack_reg);
        if (val & BIT(pd->ack_bit))
            break;
        udelay(1);
    }
    if (timeout <= 0) {
        dev_err(pmu->dev, "Domain %s ack timeout\n", genpd->name);
        clk_bulk_disable_unprepare(pd->req_clk_cnt, pd->clks);
        spin_unlock_irqrestore(&pmu->lock, flags);
        return -ETIMEDOUT;
    }
​
    spin_unlock_irqrestore(&pmu->lock, flags);
    return 0;
}
​
/**
 * @brief 断电一个电源域。
 *
 * @param genpd 指向 generic_pm_domain。
 * @return 0 成功。
 */
static int rockchip_pd_power_off(struct generic_pm_domain *genpd)
{
    struct rockchip_pm_domain *pd = container_of(genpd, struct rockchip_pm_domain, genpd);
    struct rockchip_pmu_info *pmu = genpd->dev->devdata;
    unsigned long flags;
    u32 val;
    int ret;
​
    spin_lock_irqsave(&pmu->lock, flags);
​
    // 1. 清除电源控制位 (写 0 表示断电)
    val = readl_relaxed(pmu->base + pd->pwr_reg);
    val &= ~pd->pwr_mask;
    writel_relaxed(val, pmu->base + pd->pwr_reg);
​
    // 2. 等待状态位清零
    int timeout = 2000;
    while (timeout--) {
        val = readl_relaxed(pmu->base + pd->pwr_st_reg);
        if (!(val & pd->pwr_st_mask))
            break;
        udelay(1);
    }
    if (timeout <= 0) {
        dev_err(pmu->dev, "Domain %s power off timeout\n", genpd->name);
        spin_unlock_irqrestore(&pmu->lock, flags);
        return -ETIMEDOUT;
    }
​
    // 3. 禁用相关时钟
    clk_bulk_disable_unprepare(pd->req_clk_cnt, pd->clks);
​
    spin_unlock_irqrestore(&pmu->lock, flags);
    return 0;
}
​
/**
 * @brief 读取电源域状态。
 *
 * @param genpd 指向 generic_pm_domain。
 * @return true 表示电源域处于上电状态。
 */
static bool rockchip_pd_power_state(struct generic_pm_domain *genpd)
{
    struct rockchip_pm_domain *pd = container_of(genpd, struct rockchip_pm_domain, genpd);
    struct rockchip_pmu_info *pmu = genpd->dev->devdata;
    u32 val;
​
    // 读取状态寄存器
    val = readl_relaxed(pmu->base + pd->pwr_st_reg);
    return (val & pd->pwr_st_mask) == pd->pwr_st_mask;
}
​
/**
 * @brief 初始化单个电源域。
 *
 * @param pmu 指向 rockchip_pmu_info 结构。
 * @param pd 指向 rockchip_pm_domain 结构。
 * @param node 指向设备树节点。
 * @return 0 成功。
 */
static int rockchip_pd_init(struct rockchip_pmu_info *pmu,
                            struct rockchip_pm_domain *pd,
                            struct device_node *node)
{
    struct generic_pm_domain *genpd = &pd->genpd;
    int ret;
​
    // 1. 从设备树获取寄存器偏移和位信息
    of_property_read_u32(node, "reg", &pd->pwr_reg);
    // 省略具体解析代码,实际中还需要解析 pwr_st_reg, ack_reg 等
​
    // 2. 设置 GenPD 操作回调
    genpd->name = node->name;
    genpd->power_on = rockchip_pd_power_on;
    genpd->power_off = rockchip_pd_power_off;
    genpd->power_state = rockchip_pd_power_state;
​
    // 3. 注册到 GenPD 框架
    ret = pm_genpd_init(genpd, NULL, false);
    if (ret) {
        dev_err(pmu->dev, "Failed to init genpd %s\n", node->name);
        return ret;
    }
​
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 *
 * 初始化 Power Domain 硬件,注册电源域。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功。
 */
static int rockchip_pmu_probe(struct platform_device *pdev)
{
    struct rockchip_pmu_info *pmu;
    struct device_node *np = pdev->dev.of_node, *child;
    struct resource *res;
    int ret, count = 0;
​
    // 1. 分配私有数据结构
    pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
    if (!pmu)
        return -ENOMEM;
    platform_set_drvdata(pdev, pmu);
    pmu->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;
    }
    pmu->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(pmu->base))
        return PTR_ERR(pmu->base);
​
    spin_lock_init(&pmu->lock);
​
    // 3. 遍历子节点创建电源域
    for_each_child_of_node(np, child) {
        count++;
    }
​
    pmu->domains = devm_kcalloc(&pdev->dev, count, sizeof(*pmu->domains), GFP_KERNEL);
    if (!pmu->domains)
        return -ENOMEM;
​
    pmu->num_domains = 0;
    for_each_child_of_node(np, child) {
        ret = rockchip_pd_init(pmu, &pmu->domains[pmu->num_domains], child);
        if (ret) {
            dev_err(&pdev->dev, "Failed to init domain %s\n", child->name);
            continue;
        }
        pmu->num_domains++;
    }
​
    // 4. 注册 GenPD OneCell 数据
    pmu->pd_data.domains = (struct generic_pm_domain **)pmu->domains;
    pmu->pd_data.num_domains = pmu->num_domains;
    pmu->pd_data.pd_xlate = rockchip_pd_xlate;
​
    ret = of_genpd_add_provider_onecell(np, &pmu->pd_data);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add genpd provider\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "Rockchip Power Domain controller registered at 0x%llx\n",
             (unsigned long long)res->start);
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_pmu_of_match[] = {
    { .compatible = "rockchip,rk3399-pmu" },
    { .compatible = "rockchip,rk3568-pmu" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pmu_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_pmu_driver = {
    .probe = rockchip_pmu_probe,
    .remove = rockchip_pmu_remove,
    .driver = {
        .name = "rockchip-pmu",
        .of_match_table = rockchip_pmu_of_match,
    },
};
module_platform_driver(rockchip_pmu_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Power Domain Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Power Domain 调试核心难点

3.1 电源域上电超时

现象dmesg 出现 "Domain xxx power on timeout",设备无法工作。

原因

  • 电源域依赖的时钟未使能。

  • 硬件上存在短路或电源故障。

  • 上电流程中的状态位检查逻辑错误。

调试方法

  1. 检查依赖时钟

    cat /sys/kernel/debug/clk/clk_summary | grep <domain_name>
  2. 读取电源域寄存器

    devmem2 <base>+<pwr_reg>  # 查看当前电源控制位
    devmem2 <base>+<pwr_st_reg>  # 查看电源状态位
  3. 手动强制上电

    devmem2 <base>+<pwr_reg> 0x1  # 尝试强制上电

3.2 电源域无故断电

现象:工作正常的设备突然掉电,导致系统卡死或崩溃。

原因

  • 电源域驱动由于某种原因触发了 power_off

  • 热保护或电源管理策略误判。

  • 硬件故障或电压跌落。

调试方法

  1. 检查内核日志

    dmesg | grep "power off" | grep <domain>
  2. 检查硬件状态

    devmem2 <base>+<pwr_st_reg>  # 查看电源状态

3.3 依赖关系配置错误

现象:某个域上电时,其依赖的域未上电,导致设备启动失败。

原因

  • 设备树中的 power-domains 属性配置错误。

  • 依赖链未正确解析。

调试方法

  1. 检查设备树

    dtc -I fs /sys/firmware/devicetree/base/ | grep <domain>
  2. 查看电源域拓扑

    cat /sys/kernel/debug/pm_genpd/pm_genpd_summary

3.4 电源域无法断电

现象pm_genpd_power_off 调用后,设备仍然处于工作状态,功耗未下降。

原因

  • 设备未关闭其自身对电源域的使用计数。

  • 电源域内还有活跃设备或时钟未关闭。

调试方法

  1. 查看设备计数

    cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep <domain>
  2. 强制断电

    echo "off" > /sys/class/power_supply/<domain>/state

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

场景:GPU 在系统空闲时无法进入低功耗状态,pm_genpd_summary 显示 GPU 域始终上电。

分析流程

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

    • 系统空闲时 CPU 进入空闲状态,但 GPU 域未掉电。

    • 电池续航缩短约 20%。

  2. Power Domain 层(图谱的 Power Domain 层):

    cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep gpu

    显示 GPU 域状态为 "on",且 "usage count" 不为 0。

  3. GPU 驱动层

    cat /sys/kernel/debug/dri/0/panfrost/power

    显示 GPU 正在等待 submit 完成。

  4. 根本原因

    • 用户空间应用程序在结束渲染后,未正确释放 GPU 资源。

    • GPU 驱动中的引用计数未归零,导致电源域无法断电。

  5. 解决方案

    • 增加用户空间的超时机制,强制回收资源。

    • 在 GPU 驱动中加入电源域计数增强检查。

    • 强制切断引用:

    echo "force_off" > /sys/class/power_supply/gpu/state

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

控制器 协同方式 调试关键点
Clock Controller 电源域上下电前必须使能/禁用时钟 时钟依赖、时钟计数
Reset Controller 电源域复位信号配合上下电 复位顺序、状态清理
GPU/NPU/DMA 通过电源域管理低功耗 引用计数、资源回收
Thermal 配合电源域进行热保护 过温自动断电

第七部分 DDR Memory Controller (Rockchip DMC)

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

DDR Memory Controller(DMC)是 SoC 中最关键的系统组件之一,它负责管理所有内存访问请求,包括 CPU、GPU、NPU、DMA 等核心对 DRAM 的读写操作。它通常作为 Platform 设备挂载在 SoC 内部总线上,并提供内存频率调整、带宽监控和功耗管理功能。

在 Linux 5.10 中,DDR 控制器驱动通常位于 drivers/memory/drivers/memory/rockchip/,核心层次分为三层:

  1. Devfreq 框架 (drivers/devfreq/):提供动态频率调整和性能监控。

  2. DDR 控制器驱动(本篇文章重点):具体的 SoC DDR 控制器驱动(如 rockchip-dmc.c)。

  3. 内存框架:管理内存的初始化、训练和带宽分配。

1.1 硬件关键概念

  • DDR 核心寄存器:配置内存类型、时序参数、频率、带宽控制。

  • PHY:物理层接口,负责信号调理和内存训练。

  • DVFS:动态电压频率调整,支持多个频率点。

  • 带宽监控:实时监控内存带宽使用率。

  • 功耗管理:支持低功耗模式(如自刷新、深度睡眠)。

  • ECC:纠错码支持,检测和纠正内存错误。

1.2 核心代码

// 基于 Linux 5.10 drivers/memory/rockchip/rockchip-dmc.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/devfreq.h>
#include <linux/pm_opp.h>
#include <linux/of_address.h>
​
/**
 * @brief Rockchip DDR 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 DDR Controller 硬件实例。
 */
struct rockchip_dmc {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    void __iomem *phy_base;         /**< PHY 寄存器基址 */
    int irq;                        /**< 中断号 (可选) */
    struct clk *dmc_clk;            /**< DMC 核心时钟 */
    struct clk *pclk;               /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct devfreq *devfreq;        /**< Devfreq 设备 */
    struct devfreq_dev_profile *profile; /**< Devfreq 配置 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 version;                    /**< DMC 版本号 */
    u32 bus_width;                  /**< 总线宽度 (32/64 bit) */
    u32 ddr_type;                   /**< DDR 类型 (LPDDR4/DDR4/LPDDR3) */
    u32 current_freq;               /**< 当前频率 (MHz) */
    struct dmc_timing *timings;     /**< 时序参数 */
};
​
/* 寄存器偏移量 (Rockchip DMC) */
#define DMC_CTRL                    0x00
#define DMC_STATUS                  0x04
#define DMC_CFG                     0x08
#define DMC_TIMING                  0x0C
#define DMC_BANDWIDTH               0x10
#define DMC_LOW_PWR                 0x14
#define DMC_ECC_CTRL                0x18
#define DMC_ECC_STATUS              0x1C
#define DMC_PHY_CTRL                0x100
#define DMC_PHY_STATUS              0x104
​
/**
 * @brief 初始化 DDR 控制器硬件。
 * 
 * @param dmc 指向 rockchip_dmc 结构。
 * @return 0 成功。
 */
static int rockchip_dmc_hw_init(struct rockchip_dmc *dmc)
{
    u32 ctrl;
​
    // 1. 读取 DMC 版本和配置
    dmc->version = readl(dmc->base + DMC_CTRL) & 0xFF;
    dev_info(dmc->dev, "DMC version %d\n", dmc->version);
​
    // 2. 设置总线宽度
    ctrl = readl(dmc->base + DMC_CFG);
    ctrl &= ~0x1;
    if (dmc->bus_width == 64)
        ctrl |= 0x1;
    writel(ctrl, dmc->base + DMC_CFG);
​
    // 3. 配置时序参数
    struct dmc_timing *timing = &dmc->timings[0];
    writel(timing->tRRD, dmc->base + DMC_TIMING + 0x00);
    writel(timing->tFAW, dmc->base + DMC_TIMING + 0x04);
    writel(timing->tRFC, dmc->base + DMC_TIMING + 0x08);
    writel(timing->tRAS, dmc->base + DMC_TIMING + 0x0C);
​
    // 4. 启用 ECC (如果支持)
    if (dmc->version >= 0x20) {
        u32 ecc = readl(dmc->base + DMC_ECC_CTRL);
        ecc |= 0x1;
        writel(ecc, dmc->base + DMC_ECC_CTRL);
    }
​
    // 5. 启用 DMC
    ctrl = readl(dmc->base + DMC_CTRL);
    ctrl |= 0x1;
    writel(ctrl, dmc->base + DMC_CTRL);
​
    return 0;
}
​
/**
 * @brief 设置 DDR 频率。
 * 
 * @param dmc 指向 rockchip_dmc 结构。
 * @param freq 目标频率 (MHz)。
 * @return 0 成功。
 */
static int rockchip_dmc_set_freq(struct rockchip_dmc *dmc, u32 freq)
{
    u32 ctrl;
    int ret;
​
    // 1. 检查是否支持该频率
    if (freq > 1600 || freq < 400) {
        dev_err(dmc->dev, "Invalid frequency %d MHz\n", freq);
        return -EINVAL;
    }
​
    // 2. 设置 DMC 时钟频率
    ret = clk_set_rate(dmc->dmc_clk, freq * 1000000);
    if (ret) {
        dev_err(dmc->dev, "Failed to set DMC clock\n");
        return ret;
    }
​
    // 3. 调整 PHY 配置 (根据频率优化)
    ctrl = readl(dmc->phy_base + DMC_PHY_CTRL);
    if (freq <= 800) {
        ctrl &= ~0x1;  // 低频率模式
    } else {
        ctrl |= 0x1;   // 高频率模式
    }
    writel(ctrl, dmc->phy_base + DMC_PHY_CTRL);
​
    // 4. 等待 DMC 状态稳定
    while (!(readl(dmc->base + DMC_STATUS) & 0x1))
        ;
​
    dmc->current_freq = freq;
    dev_info(dmc->dev, "DMC frequency set to %d MHz\n", freq);
​
    return 0;
}
​
/**
 * @brief 读取当前 DDR 频率。
 * 
 * @param dmc 指向 rockchip_dmc 结构。
 * @return 当前频率 (MHz)。
 */
static u32 rockchip_dmc_get_freq(struct rockchip_dmc *dmc)
{
    return dmc->current_freq;
}
​
/**
 * @brief 读取内存带宽使用率。
 * 
 * @param dmc 指向 rockchip_dmc 结构。
 * @return 带宽使用率 (0~100%)。
 */
static u32 rockchip_dmc_get_bandwidth(struct rockchip_dmc *dmc)
{
    u32 bw_reg = readl(dmc->base + DMC_BANDWIDTH);
    u32 bw = (bw_reg & 0xFFFF) * 100 / 0xFFFF;
    return bw;
}
​
/**
 * @brief Devfreq 回调:获取当前频率。
 * 
 * @param dev 指向设备。
 * @param freq 输出频率。
 * @return 0 成功。
 */
static int rockchip_dmc_get_cur_freq(struct device *dev, unsigned long *freq)
{
    struct rockchip_dmc *dmc = dev_get_drvdata(dev);
​
    *freq = rockchip_dmc_get_freq(dmc) * 1000000;
    return 0;
}
​
/**
 * @brief Devfreq 回调:设置频率。
 * 
 * @param dev 指向设备。
 * @param freq 目标频率。
 * @param flags 标志位。
 * @return 0 成功。
 */
static int rockchip_dmc_set_cur_freq(struct device *dev, unsigned long freq,
                                     u32 flags)
{
    struct rockchip_dmc *dmc = dev_get_drvdata(dev);
​
    return rockchip_dmc_set_freq(dmc, freq / 1000000);
}
​
/**
 * @brief Devfreq 回调:获取目标频率。
 * 
 * @param dev 指向设备。
 * @param freq 输出目标频率。
 * @param flags 标志位。
 * @return 0 成功。
 */
static int rockchip_dmc_target(struct device *dev, unsigned long *freq, u32 flags)
{
    // 根据带宽和负载选择最优频率
    struct rockchip_dmc *dmc = dev_get_drvdata(dev);
    u32 bw = rockchip_dmc_get_bandwidth(dmc);
​
    if (bw > 80) {
        *freq = 1600 * 1000000;  // 高负载 -> 高频率
    } else if (bw > 40) {
        *freq = 1000 * 1000000;  // 中负载 -> 中频率
    } else {
        *freq = 400 * 1000000;   // 低负载 -> 低频率
    }
​
    return 0;
}
​
/**
 * @brief Devfreq 回调:获取设备状态。
 * 
 * @param dev 指向设备。
 * @param freq 输出频率。
 * @param busy 输出繁忙时间。
 * @param total 输出总时间。
 * @return 0 成功。
 */
static int rockchip_dmc_get_dev_status(struct device *dev,
                                       struct devfreq_dev_status *stat)
{
    struct rockchip_dmc *dmc = dev_get_drvdata(dev);
​
    stat->current_frequency = rockchip_dmc_get_freq(dmc) * 1000000;
    stat->busy_time = rockchip_dmc_get_bandwidth(dmc);
    stat->total_time = 100;
​
    return 0;
}
​
/**
 * @brief DMC 中断处理函数。
 * 
 * 处理 ECC 错误、带宽监控等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_dmc 结构。
 */
static irqreturn_t rockchip_dmc_irq_handler(int irq, void *dev_id)
{
    struct rockchip_dmc *dmc = dev_id;
    u32 status;
​
    // 1. 读取 ECC 状态
    status = readl(dmc->base + DMC_ECC_STATUS);
​
    // 2. 处理 ECC 错误
    if (status & 0x1) {
        // 单比特错误 (可纠正)
        dev_warn(dmc->dev, "ECC single-bit error detected\n");
        writel(0x1, dmc->base + DMC_ECC_CTRL);
    }
    if (status & 0x2) {
        // 多比特错误 (不可纠正)
        dev_err(dmc->dev, "ECC multi-bit error detected!\n");
        writel(0x2, dmc->base + DMC_ECC_CTRL);
        // 可能需要触发系统复位
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 DDR 控制器硬件,注册 Devfreq 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_dmc_probe(struct platform_device *pdev)
{
    struct rockchip_dmc *dmc;
    struct resource *res;
    struct devfreq_dev_profile *profile;
    int ret, irq;
​
    // 1. 分配私有数据结构
    dmc = devm_kzalloc(&pdev->dev, sizeof(*dmc), GFP_KERNEL);
    if (!dmc)
        return -ENOMEM;
    platform_set_drvdata(pdev, dmc);
    dmc->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;
    }
    dmc->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dmc->base))
        return PTR_ERR(dmc->base);
​
    // 3. 获取 PHY 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        dmc->phy_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(dmc->phy_base))
            return PTR_ERR(dmc->phy_base);
    } else {
        dmc->phy_base = dmc->base + 0x1000;
    }
​
    // 4. 获取时钟
    dmc->dmc_clk = devm_clk_get(&pdev->dev, "dmc_clk");
    if (IS_ERR(dmc->dmc_clk))
        return PTR_ERR(dmc->dmc_clk);
    dmc->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(dmc->pclk))
        return PTR_ERR(dmc->pclk);
​
    clk_prepare_enable(dmc->dmc_clk);
    clk_prepare_enable(dmc->pclk);
​
    // 5. 获取中断 (ECC)
    irq = platform_get_irq(pdev, 0);
    if (irq > 0) {
        dmc->irq = irq;
        ret = devm_request_irq(&pdev->dev, dmc->irq, rockchip_dmc_irq_handler,
                               IRQF_SHARED, "rockchip-dmc", dmc);
        if (ret) {
            dev_err(&pdev->dev, "Failed to request IRQ\n");
            goto err_clk;
        }
    } else {
        dmc->irq = -1;
    }
​
    // 6. 读取 DDR 配置
    dmc->bus_width = 64;
    dmc->ddr_type = 3;  // LPDDR4
    dmc->current_freq = 800;
​
    spin_lock_init(&dmc->lock);
​
    // 7. 硬件初始化
    ret = rockchip_dmc_hw_init(dmc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init hardware\n");
        goto err_clk;
    }
​
    // 8. 设置 Devfreq 配置
    profile = devm_kzalloc(&pdev->dev, sizeof(*profile), GFP_KERNEL);
    if (!profile) {
        ret = -ENOMEM;
        goto err_clk;
    }
    profile->initial_freq = 800 * 1000000;
    profile->polling_ms = 100;
    profile->target = rockchip_dmc_target;
    profile->get_dev_status = rockchip_dmc_get_dev_status;
    profile->get_cur_freq = rockchip_dmc_get_cur_freq;
    profile->set_cur_freq = rockchip_dmc_set_cur_freq;
​
    dmc->profile = profile;
​
    // 9. 注册 Devfreq 设备
    dmc->devfreq = devm_devfreq_add_device(&pdev->dev, profile, "simple_ondemand", NULL);
    if (IS_ERR(dmc->devfreq)) {
        dev_err(&pdev->dev, "Failed to add devfreq device\n");
        ret = PTR_ERR(dmc->devfreq);
        goto err_clk;
    }
​
    dev_info(&pdev->dev, "Rockchip DDR controller registered at 0x%llx, %d bit\n",
             (unsigned long long)res->start, dmc->bus_width);
    return 0;
​
err_clk:
    clk_disable_unprepare(dmc->pclk);
    clk_disable_unprepare(dmc->dmc_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_dmc_remove(struct platform_device *pdev)
{
    struct rockchip_dmc *dmc = platform_get_drvdata(pdev);
​
    // 1. 禁用 DMC
    writel(0, dmc->base + DMC_CTRL);
​
    // 2. 禁用时钟
    clk_disable_unprepare(dmc->pclk);
    clk_disable_unprepare(dmc->dmc_clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_dmc_of_match[] = {
    { .compatible = "rockchip,rk3399-dmc" },
    { .compatible = "rockchip,rk3568-dmc" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_dmc_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_dmc_driver = {
    .probe = rockchip_dmc_probe,
    .remove = rockchip_dmc_remove,
    .driver = {
        .name = "rockchip-dmc",
        .of_match_table = rockchip_dmc_of_match,
    },
};
module_platform_driver(rockchip_dmc_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip DDR Memory Controller Driver");
MODULE_LICENSE("GPL v2");

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

3.1 内存访问异常(数据损坏)

现象:程序运行中随机崩溃,dmesg 显示 "Memory parity error" 或 "Data abort"。

原因

  • 内存时序参数配置错误。

  • 电源电压不稳定。

  • ECC 检测到不可纠正错误。

调试方法

  1. 检查 ECC 状态

    devmem2 <base>+0x1C  # DMC_ECC_STATUS
  2. 检查内存错误统计

    cat /sys/kernel/debug/dram/errors
  3. 降低内存频率

    echo 400 > /sys/devices/platform/soc/.../dmc/freq
  4. 压力测试

    # 使用 memtester 测试
    memtester 512M 10

3.2 系统卡顿或响应慢

现象:系统响应迟缓,iostat 显示内存带宽高但 CPU 占用正常。

原因

  • 内存频率自动调节策略不合理。

  • 带宽监控误判。

  • 内存控制器配置错误。

调试方法

  1. 查看当前频率

    cat /sys/devices/platform/soc/.../dmc/cur_freq
  2. 查看带宽使用率

    cat /sys/devices/platform/soc/.../dmc/bandwidth
  3. 调整 Devfreq 策略

    echo "userspace" > /sys/class/devfreq/dmc/governor
    echo 800 > /sys/class/devfreq/dmc/freq
  4. 增加频率切换阈值

    # 在驱动中调整 target 函数的阈值

3.3 低功耗模式异常

现象:系统进入睡眠后无法唤醒,或唤醒后内存数据丢失。

原因

  • 低功耗模式配置错误。

  • 自刷新 (Self-Refresh) 未正确进入。

  • 内存电源管理异常。

调试方法

  1. 检查低功耗状态

    devmem2 <base>+0x14  # DMC_LOW_PWR
  2. 手动进入低功耗

    echo 1 > /sys/devices/platform/soc/.../dmc/low_power
  3. 检查内存控制器寄存器

    devmem2 <base>+0x04  # DMC_STATUS

3.4 ECC 频繁报错

现象dmesg 中频繁出现 "ECC single-bit error detected"。

原因

  • 内存颗粒质量差或老化。

  • 电源噪声过大。

  • 内存时序配置过紧。

调试方法

  1. 查看 ECC 统计

    cat /sys/kernel/debug/dram/ecc_stats
  2. 增加 ECC 纠正阈值

    # 在 DTS 中设置 ecc-strength = <4>;
  3. 更换内存颗粒


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

场景:系统进行高负载运算时,dmesg 频繁出现 "ECC single-bit error detected",系统偶尔崩溃。

分析流程

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

    • top 显示 CPU 负载正常,但内存负载高。

    • perf top 显示 rockchip_dmc_irq_handler 占用 CPU 约 10%。

  2. DMC 层(图谱的 Device Drivers -> DDR 控制器层):

    cat /sys/kernel/debug/dram/ecc_stats

    发现 ECC 错误计数随时间线性增长。

  3. 硬件层面(Memory 硬件层):

    • 使用示波器检查 DDR 信号质量,发现时钟抖动过大。

    • 测量电源电压,发现在高负载时电压有 10% 的跌落。

  4. 根本原因

    • 电源的稳压不足,在高负载时电压跌落,导致内存访问不可靠。

    • DDR 频率设置为 1600MHz,超出内存颗粒的稳定工作范围。

  5. 解决方案

    • 降低内存频率到 1200MHz。

    • 增加电源滤波电容。

    • 开启 ECC 更积极的纠错模式。

    • 优化后,ECC 错误消失,系统稳定。


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

控制器 协同方式 调试关键点
CPU 通过 DMC 访问内存 缓存一致性、带宽争用
GPU 通过 DMC 访问帧缓冲 带宽分配、延迟敏感度
DMA 通过 DMC 访问 DMA 缓冲区 地址对齐、带宽分配
NPU 通过 DMC 访问模型权重 高带宽需求、延迟敏感
Power Management 低功耗模式切换 自刷新唤醒、数据保护

第八部分 Crypto Engine 控制器 (Rockchip Crypto)

第一章 Crypto Engine 在 Platform Bus 中的位置

Crypto Engine(也称为加密引擎)是现代 SoC 中用于硬件加速加密/解密算法(如 AES、DES、SM4)和哈希计算(如 SHA1、SHA256、SHA512、MD5)的关键协处理器。它能够比纯软件实现高出数倍的吞吐量,并减少 CPU 负载。Crypto Engine 通常作为 Platform 设备挂载在 SoC 内部总线上,通过 DMA 与系统内存交换数据。

在 Linux 5.10 中,Crypto 子系统位于 drivers/crypto/,核心层次分为三层:

  1. Crypto 核心层 (crypto/):提供通用的加密算法抽象,管理算法注册。

  2. Crypto 控制器驱动(本篇文章重点):具体的 SoC 加密硬件驱动(如 rockchip-crypto.c)。

  3. 用户空间接口:通过 /dev/crypto (AF_ALG) 提供用户空间访问。

1.1 硬件关键概念

  • AES 引擎:支持 128/192/256 位密钥,支持 ECB/CBC/CTR 模式。

  • SHA 引擎:支持 SHA1、SHA256、SHA512。

  • RSA 引擎:支持 1024/2048/4096 位模数运算。

  • DMA 引擎:支持从内存直接搬运数据到 Crypto 引擎,无需 CPU 干预。

  • 中断:处理加密/解密完成、错误等事件。

  • 时钟:需要 aclk (总线时钟) 和 hclk (主机时钟)。

1.2 核心代码

// 基于 Linux 5.10 drivers/crypto/rockchip/rk3288_crypto.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
​
/**
 * @brief Rockchip Crypto Engine 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Crypto Engine 硬件实例。
 */
struct rockchip_crypto {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    int irq;                         /**< 中断号 */
    struct clk *aclk;                /**< 总线时钟 */
    struct clk *hclk;                /**< 主机时钟 */
    struct device *dev;              /**< 设备指针 */
    struct crypto_alg *alg;          /**< 注册的加密算法 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    struct completion complete;      /**< 用于同步等待完成 */
    u8 *dma_buf;                     /**< DMA 缓冲区 */
    dma_addr_t dma_addr;             /**< DMA 缓冲区物理地址 */
    u32 buf_len;                     /**< 缓冲区长度 */
};
​
/* 寄存器偏移量 (Rockchip 加密引擎) */
#define CRYPTO_CTRL             0x00
#define CRYPTO_STATUS           0x04
#define CRYPTO_IV               0x08
#define CRYPTO_KEY              0x10
#define CRYPTO_SRC_ADDR         0x20
#define CRYPTO_DST_ADDR         0x24
#define CRYPTO_DATA_LEN         0x28
#define CRYPTO_MODE             0x2C
#define CRYPTO_INT_EN           0x30
#define CRYPTO_INT_STAT         0x34
​
/* 算法模式定义 */
#define CRYPTO_MODE_AES_ECB     0x00
#define CRYPTO_MODE_AES_CBC     0x01
#define CRYPTO_MODE_AES_CTR     0x02
#define CRYPTO_MODE_SHA1        0x10
#define CRYPTO_MODE_SHA256      0x11
​
/**
 * @brief 初始化 Crypto 引擎硬件。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 */
static void rockchip_crypto_hw_init(struct rockchip_crypto *crypto)
{
    // 1. 复位加密引擎
    writel(0x1, crypto->base + CRYPTO_CTRL);
    msleep(10);
    writel(0x0, crypto->base + CRYPTO_CTRL);
​
    // 2. 启用中断
    writel(0x1, crypto->base + CRYPTO_INT_EN);
}
​
/**
 * @brief 执行 AES 加密/解密操作。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 * @param src 源数据。
 * @param dst 目的数据。
 * @param len 数据长度。
 * @param key 密钥。
 * @param key_len 密钥长度 (128/192/256)。
 * @param iv 初始向量。
 * @param encrypt 1 为加密,0 为解密。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_run(struct rockchip_crypto *crypto,
                                   u8 *src, u8 *dst, int len,
                                   u8 *key, int key_len,
                                   u8 *iv, int encrypt)
{
    u32 ctrl, mode;
    int i;
​
    // 1. 准备 DMA 缓冲区
    memcpy(crypto->dma_buf, src, len);
    dma_map_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
​
    // 2. 配置密钥
    for (i = 0; i < key_len / 4; i++) {
        writel(((u32 *)key)[i], crypto->base + CRYPTO_KEY + i * 4);
    }
​
    // 3. 配置 IV (CBC/CTR 模式需要)
    if (iv) {
        for (i = 0; i < 4; i++) {
            writel(((u32 *)iv)[i], crypto->base + CRYPTO_IV + i * 4);
        }
    }
​
    // 4. 配置数据地址
    writel(crypto->dma_addr, crypto->base + CRYPTO_SRC_ADDR);
    writel(crypto->dma_addr, crypto->base + CRYPTO_DST_ADDR);
​
    // 5. 配置数据长度 (字节)
    writel(len, crypto->base + CRYPTO_DATA_LEN);
​
    // 6. 配置模式
    mode = 0;
    if (key_len == 192) mode |= (1 << 4);
    else if (key_len == 256) mode |= (2 << 4);
    mode |= CRYPTO_MODE_AES_CBC;  // 假设 CBC 模式
    writel(mode, crypto->base + CRYPTO_MODE);
​
    // 7. 启动加密/解密
    ctrl = (encrypt ? (1 << 0) : 0);  // 0=加密, 1=解密
    ctrl |= (1 << 1);  // 启用 DMA
    ctrl |= (1 << 2);  // 启用引擎
    writel(ctrl, crypto->base + CRYPTO_CTRL);
​
    // 8. 等待完成
    if (!wait_for_completion_timeout(&crypto->complete, 5 * HZ)) {
        dev_err(crypto->dev, "AES operation timeout\n");
        return -ETIMEDOUT;
    }
​
    // 9. 读取结果
    dma_unmap_single(crypto->dev, crypto->dma_addr, len, DMA_FROM_DEVICE);
    memcpy(dst, crypto->dma_buf, len);
​
    return 0;
}
​
/**
 * @brief 执行 SHA 哈希计算。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 * @param src 源数据。
 * @param len 数据长度。
 * @param hash 输出哈希值。
 * @param algo 哈希算法 (SHA1/SHA256)。
 * @return 0 成功。
 */
static int rockchip_crypto_sha_run(struct rockchip_crypto *crypto,
                                   u8 *src, int len,
                                   u8 *hash, int algo)
{
    u32 ctrl, mode;
    int i;
​
    // 1. 准备 DMA 缓冲区
    memcpy(crypto->dma_buf, src, len);
    dma_map_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
​
    // 2. 配置数据地址
    writel(crypto->dma_addr, crypto->base + CRYPTO_SRC_ADDR);
    writel(0, crypto->base + CRYPTO_DST_ADDR);
​
    // 3. 配置数据长度
    writel(len, crypto->base + CRYPTO_DATA_LEN);
​
    // 4. 配置模式
    if (algo == 1) {
        mode = CRYPTO_MODE_SHA1;
    } else {
        mode = CRYPTO_MODE_SHA256;
    }
    writel(mode, crypto->base + CRYPTO_MODE);
​
    // 5. 启动 SHA
    ctrl = (1 << 1) | (1 << 2);  // DMA 和引擎使能
    writel(ctrl, crypto->base + CRYPTO_CTRL);
​
    // 6. 等待完成
    if (!wait_for_completion_timeout(&crypto->complete, 5 * HZ)) {
        dev_err(crypto->dev, "SHA operation timeout\n");
        return -ETIMEDOUT;
    }
​
    // 7. 读取哈希结果 (从寄存器中读取)
    for (i = 0; i < (algo == 1 ? 5 : 8); i++) {
        ((u32 *)hash)[i] = readl(crypto->base + CRYPTO_IV + i * 4);
    }
​
    dma_unmap_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
    return 0;
}
​
/**
 * @brief AES 加密操作 (同步接口)。
 * 
 * @param req 指向 ablkcipher_request。
 * @param encrypt 1 为加密,0 为解密。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_encrypt(struct ablkcipher_request *req, int encrypt)
{
    struct crypto_ablkcipher *tfm = crypto_ablkcipher_reqtfm(req);
    struct rockchip_crypto *crypto = crypto_ablkcipher_ctx(tfm);
    struct scatterlist *src = req->src;
    struct scatterlist *dst = req->dst;
    u8 *src_buf, *dst_buf;
    int len = req->nbytes;
    int ret;
​
    // 1. 映射 scatterlist
    src_buf = kmalloc(len, GFP_KERNEL);
    dst_buf = kmalloc(len, GFP_KERNEL);
    if (!src_buf || !dst_buf) {
        ret = -ENOMEM;
        goto out;
    }
​
    sg_copy_to_buffer(src, sg_nents(src), src_buf, len);
​
    // 2. 执行硬件加密
    ret = rockchip_crypto_aes_run(crypto, src_buf, dst_buf, len,
                                   crypto->key, crypto->key_len,
                                   req->info, encrypt);
​
    // 3. 将结果写回 destination
    if (ret == 0) {
        sg_copy_from_buffer(dst, sg_nents(dst), dst_buf, len);
    }
​
out:
    kfree(src_buf);
    kfree(dst_buf);
    return ret;
}
​
/**
 * @brief AES 加密请求。
 * 
 * @param req 指向 ablkcipher_request。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_encrypt_req(struct ablkcipher_request *req)
{
    return rockchip_crypto_aes_encrypt(req, 1);
}
​
/**
 * @brief AES 解密请求。
 * 
 * @param req 指向 ablkcipher_request。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_decrypt_req(struct ablkcipher_request *req)
{
    return rockchip_crypto_aes_encrypt(req, 0);
}
​
/**
 * @brief 中断处理函数。
 * 
 * 处理加密/解密完成、错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_crypto 结构。
 */
static irqreturn_t rockchip_crypto_irq_handler(int irq, void *dev_id)
{
    struct rockchip_crypto *crypto = dev_id;
    u32 status;
​
    // 1. 读取中断状态
    status = readl(crypto->base + CRYPTO_INT_STAT);
​
    // 2. 处理完成中断
    if (status & (1 << 0)) {
        // 清除中断
        writel(1 << 0, crypto->base + CRYPTO_INT_STAT);
        // 通知等待的线程
        complete(&crypto->complete);
    }
​
    // 3. 处理错误中断
    if (status & (1 << 1)) {
        dev_err(crypto->dev, "Crypto engine error\n");
        writel(1 << 1, crypto->base + CRYPTO_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Crypto 引擎硬件,注册算法。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_crypto_probe(struct platform_device *pdev)
{
    struct rockchip_crypto *crypto;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    crypto = devm_kzalloc(&pdev->dev, sizeof(*crypto), GFP_KERNEL);
    if (!crypto)
        return -ENOMEM;
    platform_set_drvdata(pdev, crypto);
    crypto->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;
    }
    crypto->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(crypto->base))
        return PTR_ERR(crypto->base);
​
    // 3. 获取时钟
    crypto->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(crypto->aclk))
        return PTR_ERR(crypto->aclk);
    crypto->hclk = devm_clk_get(&pdev->dev, "hclk");
    if (IS_ERR(crypto->hclk))
        return PTR_ERR(crypto->hclk);
​
    clk_prepare_enable(crypto->aclk);
    clk_prepare_enable(crypto->hclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    crypto->irq = irq;
​
    // 5. 分配 DMA 缓冲区
    crypto->buf_len = PAGE_SIZE;
    crypto->dma_buf = dma_alloc_coherent(&pdev->dev, crypto->buf_len,
                                          &crypto->dma_addr, GFP_KERNEL);
    if (!crypto->dma_buf) {
        dev_err(&pdev->dev, "Failed to allocate DMA buffer\n");
        ret = -ENOMEM;
        goto err_clk;
    }
​
    // 6. 初始化完成量
    init_completion(&crypto->complete);
    spin_lock_init(&crypto->lock);
​
    // 7. 硬件初始化
    rockchip_crypto_hw_init(crypto);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, crypto->irq, rockchip_crypto_irq_handler,
                           IRQF_SHARED, "rockchip-crypto", crypto);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_dma;
    }
​
    // 9. 注册 AES 算法
    struct crypto_alg alg = {
        .cra_name = "aes-cbc",
        .cra_driver_name = "rockchip-aes-cbc",
        .cra_priority = 300,
        .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER,
        .cra_blocksize = AES_BLOCK_SIZE,
        .cra_ctxsize = sizeof(struct rockchip_crypto),
        .cra_module = THIS_MODULE,
        .cra_ablkcipher = {
            .min_keysize = AES_MIN_KEY_SIZE,
            .max_keysize = AES_MAX_KEY_SIZE,
            .ivsize = AES_BLOCK_SIZE,
            .setkey = rockchip_crypto_aes_setkey,
            .encrypt = rockchip_crypto_aes_encrypt_req,
            .decrypt = rockchip_crypto_aes_decrypt_req,
        },
    };
​
    ret = crypto_register_alg(&alg);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register AES algorithm\n");
        goto err_irq;
    }
    crypto->alg = &alg;
​
    dev_info(&pdev->dev, "Rockchip Crypto Engine registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, crypto->irq);
    return 0;
​
err_irq:
    devm_free_irq(&pdev->dev, crypto->irq, crypto);
err_dma:
    dma_free_coherent(&pdev->dev, crypto->buf_len, crypto->dma_buf, crypto->dma_addr);
err_clk:
    clk_disable_unprepare(crypto->hclk);
    clk_disable_unprepare(crypto->aclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_crypto_remove(struct platform_device *pdev)
{
    struct rockchip_crypto *crypto = platform_get_drvdata(pdev);
​
    // 1. 注销算法
    if (crypto->alg)
        crypto_unregister_alg(crypto->alg);
​
    // 2. 释放 DMA 缓冲区
    dma_free_coherent(&pdev->dev, crypto->buf_len, crypto->dma_buf, crypto->dma_addr);
​
    // 3. 禁用时钟
    clk_disable_unprepare(crypto->hclk);
    clk_disable_unprepare(crypto->aclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_crypto_of_match[] = {
    { .compatible = "rockchip,rk3288-crypto" },
    { .compatible = "rockchip,rk3399-crypto" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_crypto_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_crypto_driver = {
    .probe = rockchip_crypto_probe,
    .remove = rockchip_crypto_remove,
    .driver = {
        .name = "rockchip-crypto",
        .of_match_table = rockchip_crypto_of_match,
    },
};
module_platform_driver(rockchip_crypto_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Crypto Engine Driver");
MODULE_LICENSE("GPL v2");

第三章 Crypto Engine 调试核心难点

3.1 算法调用失败

现象:应用程序调用加密 API 时,返回 -EINVAL-ENOTSUPP

原因

  • 算法未在 Crypto 引擎中注册。

  • 密钥长度不支持。

  • 模式不支持(如 CTR 模式未在硬件中实现)。

调试方法

  1. 检查算法注册

    cat /proc/crypto | grep rockchip
  2. 测试算法支持

    # 使用 cryptsetup 测试 AES 加密
    cryptsetup luksFormat --cipher aes-cbc-essiv:sha256 /dev/sda
  3. 查看日志

    dmesg | grep crypto

3.2 DMA 传输错误

现象:加密操作卡住,dmesg 出现 "DMA mapping error" 或 "Operation timeout"。

原因

  • DMA 缓冲区未对齐到硬件要求(某些硬件需要 16 或 64 字节对齐)。

  • 源数据长度不是 16 字节的倍数(AES 块大小要求)。

  • DMA 地址超过 32 位范围。

调试方法

  1. 检查 DMA 对齐

    # 检查缓冲区地址
    cat /sys/kernel/debug/dma/dma-* | grep crypto
  2. 调整数据长度:在驱动中填充数据到 16 字节倍数。

  3. 使用 perf 跟踪 DMA 中断

    perf record -e dma:* -a -- timeout 5
    perf script | grep crypto

3.3 性能瓶颈(吞吐量低)

现象openssl speed aes-256-cbc 测速结果远低于硬件理论值。

原因

  • 中断开销过高。

  • 每次操作都经过 DMA 映射/取消映射。

  • 未使用批量处理或请求队列。

调试方法

  1. 查看中断频率

    cat /proc/interrupts | grep crypto
  2. 增加批处理大小:在驱动中合并多个请求。

  3. 使用 perf 分析热点

    perf record -e cycles -g openssl speed aes-256-cbc
    perf report

3.4 哈希结果错误

现象:使用 SHA 哈希得到的结果与软件实现不一致。

原因

  • 哈希状态未正确初始化。

  • 数据长度处理错误(SHA 需要填充)。

  • 字节序问题(大端/小端)。

调试方法

  1. 对比软件计算结果

    # 使用 openssl 计算
    echo -n "test" | openssl sha256
  2. 调试寄存器状态

    devmem2 <base>+0x10  # CRYPTO_IV 寄存器
  3. 检查初始化向量:确保每次哈希开始前清零。


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

场景:在系统负载高时,加密操作偶尔失败,dmesg 出现 "AES operation timeout"。

分析流程

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

    • top 显示 kworker/crypto 进程占用 CPU 较高。

    • perf top 显示 rockchip_crypto_irq_handler 占用 30% CPU。

  2. Crypto 层(图谱的 Device Drivers -> Crypto Engine 层):

    cat /proc/interrupts | grep crypto

    发现中断触发频率很高(5000+ 次/秒)。

  3. DMA 层(DMA 控制器层):

    trace-cmd record -e dma:* -e crypto:* -a -- timeout 5
    trace-cmd report | grep rockchip_crypto

    发现 DMA 中断处理延迟超过 1ms。

  4. 根本原因

    • 每次加密操作都需要重新映射 DMA 缓冲区,DMA 映射和取消映射操作频繁。

    • 加密请求频率高,中断处理无法及时完成所有请求。

    • 中断处理程序在释放 DMA 映射时被较高优先级任务抢占。

  5. 解决方案

    • 实现请求队列,批量处理加密操作。

    • 使用 DMA 池预分配 DMA 缓冲区,减少映射开销。

    • 提高中断处理优先级。

    • 优化后,吞吐量提升 4 倍,无超时错误。


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

控制器 协同方式 调试关键点
DMA 数据传输搬运 DMA 对齐、映射开销、中断延迟
Clock 提供总线时钟 时钟频率匹配
Interrupt Controller 管理加密完成中断 中断优先级、处理延迟
Memory Controller 访问加密数据 内存一致性、缓存策略

第九部分 Pinctrl 控制器 (Rockchip Pinctrl)

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

Pinctrl 控制器负责管理 SoC 的所有引脚(GPIO、UART、I2C、SPI、PWM 等)。每个引脚可以被配置为不同的功能(复用)、方向、驱动强度、上拉/下拉电阻等。它作为 Platform 设备挂载在 SoC 内部总线上,是所有外设驱动依赖的底层基础设施。

在 Linux 5.10 中,Pinctrl 子系统位于 drivers/pinctrl/,核心层次分为三层:

  1. Pinctrl 核心层 (drivers/pinctrl/core.c):提供 pin 控制、复用、配置的通用 API。

  2. Pinctrl 控制器驱动(本篇文章重点):具体的 SoC Pin 控制器驱动(如 pinctrl-rockchip.c)。

  3. Pinctrl 客户端驱动:使用 pinctrl_get/select_state 配置引脚的外设驱动(如 dw_i2cdw_spi)。

1.1 硬件关键概念

  • PIN 与 Bank:引脚按照 Bank 分组,每个 Bank 包含 32 个引脚。

  • Pinmux:引脚功能复用,每个引脚可以有多个功能(如 GPIO、UART、I2C、SPI)。

  • Pinconf:引脚电气属性配置(驱动强度、上拉/下拉、输出/输入、中断极性)。

  • Pull 电阻:支持上拉、下拉、浮空。

  • 驱动强度:支持 4mA、8mA、12mA 等。

  • 中断:每个引脚都可以配置为中断源,通过 GPIO 中断控制器路由到 GIC。

1.2 核心代码

// 基于 Linux 5.10 drivers/pinctrl/pinctrl-rockchip.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
​
/**
 * @brief Rockchip Pinctrl 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Pinctrl 硬件实例。
 */
struct rockchip_pinctrl {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    struct device *dev;              /**< 设备指针 */
    struct pinctrl_dev *pctl;        /**< Pinctrl 设备抽象 */
    struct pinctrl_desc *desc;       /**< Pinctrl 描述符 */
    struct rockchip_pin_bank *banks; /**< Bank 数组 */
    u32 nbanks;                      /**< Bank 数量 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    struct gpio_chip gpio_chip;      /**< GPIO 芯片抽象 */
};
​
/* 寄存器偏移量 (Rockchip Pinctrl) */
#define PINCTRL_GPIO_BASE           0x0000
#define PINCTRL_GPIOBANK_OFFSET     0x1000
​
/* 每个 Bank 的寄存器偏移 */
#define GPIO_SWPORT_DR              0x00
#define GPIO_SWPORT_DDR             0x04
#define GPIO_INTEN                  0x30
#define GPIO_INTMASK                0x34
#define GPIO_INTTYPE_LEVEL          0x38
#define GPIO_INTTYPE_EDGE           0x3C
#define GPIO_INT_POLARITY           0x40
#define GPIO_INT_STATUS             0x44
​
/**
 * @brief 配置单个引脚的功能复用。
 * 
 * @param pinctrl 指向 rockchip_pinctrl 结构。
 * @param bank Bank 号。
 * @param pin 引脚号 (0~31)。
 * @param func 功能号 (0=GPIO, 1=UART, 2=I2C, 3=SPI, ...)。
 */
static int rockchip_pinmux_set(struct rockchip_pinctrl *pinctrl,
                               u32 bank, u32 pin, u32 func)
{
    void __iomem *base = pinctrl->base + bank * PINCTRL_GPIOBANK_OFFSET;
    u32 reg = base + 0x100 + (pin / 8) * 4;
    u32 shift = (pin % 8) * 4;
    u32 val;
​
    // 1. 读取当前复用寄存器
    val = readl(reg);
    val &= ~(0xF << shift);
    val |= (func & 0xF) << shift;
    writel(val, reg);
​
    return 0;
}
​
/**
 * @brief 配置引脚电气属性(驱动强度、上拉等)。
 * 
 * @param pinctrl 指向 rockchip_pinctrl 结构。
 * @param bank Bank 号。
 * @param pin 引脚号。
 * @param drv 驱动强度 (0=4mA, 1=8mA, 2=12mA)。
 * @param pull 上拉类型 (0=浮空, 1=上拉, 2=下拉)。
 */
static int rockchip_pinconf_set(struct rockchip_pinctrl *pinctrl,
                                u32 bank, u32 pin, u32 drv, u32 pull)
{
    void __iomem *base = pinctrl->base + bank * PINCTRL_GPIOBANK_OFFSET;
​
    // 1. 配置驱动强度
    if (drv < 3) {
        u32 reg = base + 0x200 + (pin / 4) * 4;
        u32 shift = (pin % 4) * 2;
        u32 val = readl(reg);
        val &= ~(0x3 << shift);
        val |= (drv & 0x3) << shift;
        writel(val, reg);
    }
​
    // 2. 配置上拉/下拉
    if (pull < 3) {
        u32 reg = base + 0x300 + (pin / 4) * 4;
        u32 shift = (pin % 4) * 2;
        u32 val = readl(reg);
        val &= ~(0x3 << shift);
        val |= (pull & 0x3) << shift;
        writel(val, reg);
    }
​
    return 0;
}
​
/**
 * @brief Pinctrl 回调:设置引脚复用。
 * 
 * @param pctldev 指向 pinctrl_dev。
 * @param selector 功能选择器。
 * @param grp_selector 组选择器。
 * @return 0 成功。
 */
static int rockchip_pinmux_set_mux(struct pinctrl_dev *pctldev,
                                   unsigned int selector,
                                   unsigned int grp_selector)
{
    struct rockchip_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctldev);
    struct function_desc *func = pinctrl->desc->functions[selector];
    struct group_desc *grp = pinctrl->desc->groups[grp_selector];
    struct pin_group *group = grp->data;
    int i;
​
    // 遍历组中的所有引脚,配置复用
    for (i = 0; i < group->npins; i++) {
        u32 pin = group->pins[i];
        u32 bank = pin / 32;
        u32 pin_idx = pin % 32;
        rockchip_pinmux_set(pinctrl, bank, pin_idx, selector);
    }
​
    return 0;
}
​
/**
 * @brief Pinctrl 回调:配置引脚电气属性。
 * 
 * @param pctldev 指向 pinctrl_dev。
 * @param pin 引脚号。
 * @param configs 配置数组。
 * @param num_configs 配置数量。
 * @return 0 成功。
 */
static int rockchip_pinconf_set(struct pinctrl_dev *pctldev,
                                unsigned int pin,
                                unsigned long *configs,
                                unsigned int num_configs)
{
    struct rockchip_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctldev);
    u32 bank = pin / 32;
    u32 pin_idx = pin % 32;
    u32 drv = 1;  // 默认 8mA
    u32 pull = 0; // 默认浮空
    int i;
​
    for (i = 0; i < num_configs; i++) {
        enum pin_config_param param = pinconf_to_config_param(configs[i]);
        u32 val = pinconf_to_config_argument(configs[i]);
​
        switch (param) {
        case PIN_CONFIG_DRIVE_STRENGTH:
            if (val <= 4) drv = 0;
            else if (val <= 8) drv = 1;
            else drv = 2;
            break;
        case PIN_CONFIG_BIAS_PULL_UP:
            pull = 1;
            break;
        case PIN_CONFIG_BIAS_PULL_DOWN:
            pull = 2;
            break;
        case PIN_CONFIG_BIAS_DISABLE:
            pull = 0;
            break;
        default:
            continue;
        }
    }
​
    return rockchip_pinconf_set(pinctrl, bank, pin_idx, drv, pull);
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Pinctrl 硬件,注册到 Pinctrl 核心。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
    struct rockchip_pinctrl *pinctrl;
    struct resource *res;
    int ret;
​
    // 1. 分配私有数据结构
    pinctrl = devm_kzalloc(&pdev->dev, sizeof(*pinctrl), GFP_KERNEL);
    if (!pinctrl)
        return -ENOMEM;
    platform_set_drvdata(pdev, pinctrl);
    pinctrl->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;
    }
    pinctrl->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(pinctrl->base))
        return PTR_ERR(pinctrl->base);
​
    spin_lock_init(&pinctrl->lock);
​
    // 3. 读取 Bank 数量(从设备树)
    pinctrl->nbanks = 4;  // 示例:4 个 Bank
​
    // 4. 初始化 Pinctrl 描述符
    pinctrl->desc = devm_kzalloc(&pdev->dev, sizeof(struct pinctrl_desc), GFP_KERNEL);
    if (!pinctrl->desc)
        return -ENOMEM;
​
    pinctrl->desc->name = "rockchip-pinctrl";
    pinctrl->desc->pins = rockchip_pinctrl_pins;
    pinctrl->desc->npins = ARRAY_SIZE(rockchip_pinctrl_pins);
    pinctrl->desc->groups = rockchip_pinctrl_groups;
    pinctrl->desc->ngroups = ARRAY_SIZE(rockchip_pinctrl_groups);
    pinctrl->desc->functions = rockchip_pinctrl_functions;
    pinctrl->desc->nfunctions = ARRAY_SIZE(rockchip_pinctrl_functions);
​
    // 5. 注册 Pinctrl 设备
    pinctrl->pctl = devm_pinctrl_register(&pdev->dev, pinctrl->desc, pinctrl);
    if (IS_ERR(pinctrl->pctl)) {
        dev_err(&pdev->dev, "Failed to register pinctrl\n");
        return PTR_ERR(pinctrl->pctl);
    }
​
    // 6. 注册 GPIO 芯片(可选,Pinctrl 常集成 GPIO 功能)
    // 此处省略具体的 gpiochip_register 代码
​
    dev_info(&pdev->dev, "Rockchip Pinctrl controller registered at 0x%llx\n",
             (unsigned long long)res->start);
    return 0;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_pinctrl_remove(struct platform_device *pdev)
{
    struct rockchip_pinctrl *pinctrl = platform_get_drvdata(pdev);
​
    // 1. 注销 Pinctrl 设备
    pinctrl_unregister(pinctrl->pctl);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_pinctrl_of_match[] = {
    { .compatible = "rockchip,rk3399-pinctrl" },
    { .compatible = "rockchip,rk3568-pinctrl" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pinctrl_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_pinctrl_driver = {
    .probe = rockchip_pinctrl_probe,
    .remove = rockchip_pinctrl_remove,
    .driver = {
        .name = "rockchip-pinctrl",
        .of_match_table = rockchip_pinctrl_of_match,
    },
};
module_platform_driver(rockchip_pinctrl_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Pinctrl Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Pinctrl 调试核心难点

3.1 引脚功能冲突

现象:某个外设无法工作,dmesg 显示 "pin already used by"。

原因

  • 两个不同的设备尝试使用同一个引脚。

  • 设备树中 pinctrl-namespinctrl-0 配置错误。

  • 引脚被 Bootloader 占用且未释放。

调试方法

  1. 查看引脚使用情况

    cat /sys/kernel/debug/pinctrl/pinctrl-handles
  2. 查看引脚状态

    cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins
  3. 检查设备树配置

    dtc -I fs /sys/firmware/devicetree/base/ | grep <pin_name>

3.2 中断配置错误

现象:GPIO 中断无法触发,cat /proc/interrupts 无计数。

原因

  • 中断类型配置错误(边沿 vs 电平)。

  • GPIO 中断未使能。

  • 引脚被配置为非中断功能。

调试方法

  1. 检查 GPIO 中断配置

    devmem2 <base>+0x30  # GPIO_INTEN
    devmem2 <base>+0x38  # GPIO_INTTYPE_LEVEL
    devmem2 <base>+0x3C  # GPIO_INTTYPE_EDGE
  2. 手动触发中断

    # 通过 devmem2 强制写入中断状态
    devmem2 <base>+0x44 0x1

3.3 驱动强度导致信号异常

现象:I2C 或 SPI 设备通信不稳定,信号波形异常。

原因

  • 驱动强度过弱。

  • 上拉电阻配置错误。

  • 外部负载过大。

调试方法

  1. 检查驱动强度

    devmem2 <base>+0x200  # 驱动强度寄存器
  2. 检查上拉配置

    devmem2 <base>+0x300  # 上拉寄存器
  3. 调整驱动强度

    # 通过 sysfs 调整
    echo "1000" > /sys/class/pinctrl/rockchip-pinctrl/pinconf/set_drive

3.4 复位后引脚状态恢复失败

现象:系统复位后,某些外设引脚功能丢失。

原因

  • Bootloader 未正确设置引脚状态。

  • 内核中的 Pinctrl 配置未生效。

  • 设备树中的 default 状态缺失。

调试方法

  1. 检查内核引脚的初始状态

    cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins
  2. 强制应用配置

    echo 1 > /sys/kernel/debug/pinctrl/rockchip-pinctrl/force_config

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

场景:I2C 总线上的传感器无法访问,i2cdetect -y 0 无响应。

分析流程

  1. 宏观层面(图谱的 Device Drivers -> I2C 层):

    • dmesg | grep i2c 显示 "I2C bus 0 no response"。

    • perf top 未显示异常。

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

    cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins | grep i2c

    发现 I2C 的 SCL 和 SDA 引脚被配置为 GPIO 而不是 I2C 功能。

  3. 设备树检查

    dtc -I fs /sys/firmware/devicetree/base/ | grep i2c

    发现 I2C 节点的 pinctrl-names 配置为 "default",但 pinctrl-0 指向的引脚组不存在。

  4. 根本原因

    • 设备树中 I2C 引脚组配置错误,导致内核无法正确设置引脚复用功能。

    • Bootloader 没有保留正确的 I2C 引脚配置。

  5. 解决方案

    • 修正设备树中 I2C 的 pinctrl-0 配置,指向正确的引脚组。

    • 重新编译设备树并烧录。

    • 通过内核命令行强制应用配置。


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

控制器 协同方式 调试关键点
GPIO Pinctrl 通常集成 GPIO 功能 引脚中断、方向配置
I2C/SPI/UART 通过 Pinctrl 配置引脚功能 复用选择、驱动强度
PWM 通过 Pinctrl 配置 PWM 引脚 复用选择、上拉/下拉
Interrupt Controller (GIC) GPIO 中断通过 GIC 路由 中断类型、触发方式
Reset Controller 复位后引脚状态丢失 复位后重新配置
Logo

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

更多推荐