Linux Platform 总线设备驱动模型之 SDHCI Reset Mailbox GIC IOMMU/SMMU Power Domain DDR Memory Controller Crypt
第一部分 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 框架分为三层:
-
SDHCI 核心层 (
drivers/mmc/host/sdhci.c):提供标准的 SDHCI 操作(命令、数据、中断、DMA)。 -
SDHCI 平台驱动(本篇文章重点):具体 SoC 的 SDHCI 驱动(如
sdhci-of-dwcmshc.c)。 -
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 未正确配置。
-
卡供电未使能。
-
卡检测中断未触发。
调试方法:
-
检查卡检测 GPIO:
cat /sys/kernel/debug/gpio | grep cd
-
强制检测卡:
echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/scan
-
检查卡供电:
cat /sys/kernel/debug/regulator/regulator_summary | grep mmc
3.2 数据传输 CRC 错误
现象:dmesg 频繁出现 "CRC error",读写速度慢。
原因:
-
SD 总线信号质量差。
-
时钟频率过高。
-
布线问题。
调试方法:
-
降低时钟频率:
# 在 DTS 中设置 max-frequency = <10000000>;
-
降低总线宽度:
echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/mmc0:0001/bus_width
-
检查信号完整性:用示波器查看 SD 时钟和数据线。
3.3 DMA 传输失败
现象:大量数据传输时出错,dmesg 显示 "DMA error"。
原因:
-
ADMA 描述符表对齐错误。
-
DMA 地址超过 32 位范围。
-
DMA 中断处理超时。
调试方法:
-
检查 ADMA 描述符:
devmem2 <base>+0x50 # ADMA_LOW
-
使用 PIO 模式测试:在驱动中强制禁用 DMA。
-
调试 DMA 中断:
trace-cmd record -e dma:* -a -- timeout 5 trace-cmd report | grep dwcmshc
第四章 结合性能图谱的调试场景示例
场景:使用 eMMC 读写文件时,速度只有 50MB/s,理论上应达 300MB/s。
分析流程:
-
宏观层面(图谱的 CPU 和 I/O 层):
-
perf top显示sdhci_irq_handler占用 CPU 约 30%。 -
iostat -x 1显示 mmcblk0 的r/s和w/s较高,但吞吐量低。
-
-
SDHCI 层(图谱的 Device Drivers -> SDHCI 层):
cat /sys/kernel/debug/mmc0/ios
发现当前频率为 50MHz,总线宽度为 4-bit,理论带宽约 200MB/s。
-
时钟配置(Clock 层):
cat /sys/kernel/debug/clk/clk_summary | grep core
发现
core_clk频率仅为 100MHz。 -
根本原因:
-
eMMC 芯片支持 HS200 模式(200MHz),但实际时钟被限制在 50MHz。
-
SDHCI 驱动中未正确启用 HS200 模式。
-
未执行调优 (Tuning) 流程。
-
-
解决方案:
-
在 DTS 中设置
cap-mmc-hw-reset和mmc-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/,核心层次分为三层:
-
Reset 核心层 (
drivers/reset/core.c):提供复位控制器抽象、设备树解析和 API。 -
Reset 控制器驱动(本篇文章重点):具体的 SoC Reset 控制器(如
reset-rockchip.c)。 -
复位客户端驱动:调用复位 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 成功,但复位释放后外设仍然无法工作。
原因:
-
复位后未正确配置时钟。
-
复位信号未正确释放(低电平保持)。
-
硬件设计缺陷(复位引脚未连接)。
调试方法:
-
检查复位状态:
devmem2 <base>+<bank_offset> # 读取复位寄存器
-
强制释放复位:
# 直接写寄存器强制释放 devmem2 <base>+<bank_offset> 0xFFFFFFFF
-
检查时钟:
# 确保外设时钟已使能 cat /sys/kernel/debug/clk/clk_summary | grep <device_name>
-
检查复位序列:确保
reset→udelay→deassert之间有足够延时。
3.2 复位 ID 映射错误
现象:复位控制器的 ID 映射错误,导致复位了错误的外设,系统不稳定。
原因:
-
DTS 中
reset-names与resets属性顺序不匹配。 -
驱动中的 ID 映射表与 DTS 不一致。
调试方法:
-
查看 DTS 映射:
# 查看设备树复位属性 dtc -I fs /sys/firmware/devicetree/base/ | grep resets
-
打印 ID 映射:
# 在驱动中添加调试打印 dev_info(reset->dev, "Reset ID: %lu -> bank %d bit %d\n", id, bank, bit);
3.3 复位不生效(复位信号被锁定)
现象:devm_reset_control_assert 后,外设仍处于工作状态,未复位。
原因:
-
复位控制器本身处于复位状态。
-
SoC 的复位逻辑被锁定(如写保护)。
-
外设自带有独立复位逻辑。
调试方法:
-
检查 CRU 写保护:
devmem2 <base>+0x0 # 读取 CRU 控制寄存器
-
手动解除写保护:
# 通常 CRU 有写保护密钥 devmem2 <base>+0x0 0xDEADBEEF
-
使用逻辑分析仪:观察复位引脚的变化。
第四章 结合性能调试场景示例
场景:系统启动后,I2C 控制器无法被内核驱动识别,i2cdetect -y 0 出现 No i2c device。DTS 中 I2C 配置正常。
分析流程:
-
宏观层面(图谱的 CPU 层):
-
dmesg | grep i2c显示 "Failed to probe"。 -
perf top显示rockchip_reset_reset未被调用。
-
-
Reset 层(图谱的 Device Drivers -> Reset Controller 层):
devmem2 <reset_base>+<bank_offset> # 读取复位寄存器
发现 I2C 对应的复位位为 0 (复位状态)。
-
I2C 驱动层(I2C 控制器层):
cat /sys/kernel/debug/clk/clk_summary | grep i2c
发现 I2C 时钟已使能。
-
根本原因:
-
Bootloader 在初始化时,对 I2C 控制器执行了复位操作,但复位信号未被释放。
-
内核的 I2C 驱动在
probe中仅配置了时钟,未调用reset_control_deassert。
-
-
解决方案:
-
在 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/,核心层次分为三层:
-
Mailbox 核心层 (
drivers/mailbox/mailbox.c):提供通用的 Mailbox 控制器抽象。 -
Mailbox 控制器驱动(本篇文章重点):具体的 SoC Mailbox 硬件驱动(如
rockchip-mailbox.c)。 -
Mailbox 客户端驱动:使用 Mailbox 发送消息的驱动(如
rpmsg、remoteproc)。
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 消息丢失
现象:发送端确认消息已发送,但接收端没有收到消息,或中断未触发。
原因:
-
通道被占用(发送端忙)。
-
接收端中断被屏蔽。
-
发送端和接收端的消息寄存器地址映射不同。
调试方法:
-
检查通道状态:
devmem2 <base>+0x04 # MAILBOX_STATUS
-
检查中断状态:
devmem2 <base>+0x0C # MAILBOX_INT_STAT
-
使用轮询模式测试:在驱动中临时切换轮询模式,验证中断问题。
3.2 中断风暴
现象:Mailbox 中断频繁触发,CPU 占用率高。
原因:
-
接收端未及时清除 RX 状态。
-
发送端反复发送相同消息。
-
硬件环路测试模式被意外启用。
调试方法:
-
查看中断统计:
cat /proc/interrupts | grep mailbox
-
检查 RX 确认:
devmem2 <base>+0x20 # MAILBOX_RX_CONFIRM
3.3 通道分配冲突
现象:多个客户端使用同一 Mailbox 通道,导致消息冲突。
原因:
-
DTS 中未正确指定
mbox-names。 -
客户端驱动未正确处理通道分配。
调试方法:
-
检查 DTS 配置:
dtc -I fs /sys/firmware/devicetree/base/ | grep mbox
-
跟踪客户端使用:
trace-cmd record -e mailbox:* -a -- timeout 5 trace-cmd report | grep channel
第四章 结合性能调试场景示例
场景:Cortex-A 核心与 Cortex-M 核心通过 Mailbox 通信,但偶尔出现消息滞后的现象。
分析流程:
-
宏观层面(图谱的 CPU 层):
-
perf top显示rockchip_mailbox_irq_handler占用 CPU 约 15%。 -
中断频率约 1000 次/秒。
-
-
Mailbox 层(Device Drivers -> Mailbox 层):
devmem2 <base>+0x0C # MAILBOX_INT_STAT
发现通道 0 的 RX 中断频繁触发。
-
客户端驱动(RPMsg 层):
cat /sys/kernel/debug/rpmsg/rpmsg0/status
显示队列长度接近上限。
-
根本原因:
-
Mailbox 通道的缓冲区过小(仅 32 位),无法存储多帧数据。
-
客户端驱动未及时处理消息,导致接收端中断被阻塞。
-
-
解决方案:
-
增加客户端驱动的接收队列深度。
-
启用 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/,核心层次分为三层:
-
IRQ 核心层 (
kernel/irq/):提供中断号分配、中断处理、中断线程化等通用 API。 -
GIC 控制器驱动(本篇文章重点):具体的 GIC 硬件驱动(如
irq-gic.c、irq-gic-v3.c)。 -
中断客户端驱动:使用
request_irq或devm_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 中未启用。
-
中断号映射错误。
调试方法:
-
检查 GIC 状态:
devmem2 <dist_base>+0x04 # GIC_DIST_CTYPE
-
检查中断使能:
devmem2 <dist_base>+0x100 # GIC_DIST_ENABLE_SET
-
检查触发方式:
devmem2 <dist_base>+0xC00 # GIC_DIST_CONFIG
-
手动触发中断:
# 软件生成中断 devmem2 <dist_base>+0xF00 0x01
3.2 中断风暴
现象:特定中断号触发频繁,CPU 占用高,中断计数异常。
原因:
-
外设中断未清除状态。
-
中断配置为边缘触发但电平信号持续。
-
中断优先级被设置为最高。
调试方法:
-
查看中断统计:
cat /proc/interrupts | grep <irq_num>
-
临时屏蔽中断:
echo <irq_num> > /proc/irq/<irq_num>/disable
-
检查外设寄存器:确认中断状态已清除。
3.3 中断亲和性配置失败
现象:中断无法路由到指定 CPU 核心。
原因:
-
GIC 版本不支持中断亲和性。
-
中断类型为 PPI(私有中断,不可路由)。
-
亲和性配置被内核屏蔽。
调试方法:
-
检查 GIC 版本:
cat /sys/kernel/debug/irq/irq_domain_mapping
-
检查中断类型:
cat /proc/interrupts | grep "PPI"
-
强制设置亲和性:
echo 2 > /proc/irq/<irq_num>/smp_affinity
3.4 中断延迟过高
现象:中断响应延迟高,perf 显示中断处理延迟。
原因:
-
中断优先级被屏蔽。
-
高优先级中断抢占。
-
中断被调度到繁忙的 CPU 核心。
调试方法:
-
测量中断延迟:
cyclictest -p 99 -t 5 -n
-
检查中断优先级:
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/,核心层次分为三层:
-
IOMMU 核心层 (
drivers/iommu/iommu.c):提供通用的 IOMMU 抽象 API。 -
SMMU 控制器驱动(本篇文章重点):具体的 SMMU 硬件驱动(如
arm-smmu.c)。 -
IOMMU 客户端驱动:使用 iommu_map/unmap 的外设驱动(如
panfrost、drm)。
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 域。
调试方法:
-
检查 SMMU 错误状态:
devmem2 <base>+0x04 # SMMU_STATUS
-
读取 fault 地址:
devmem2 <base>+0x100 # 错误地址寄存器
-
检查 Stream ID:
# 从设备树检查 stream-id dtc -I fs /sys/firmware/devicetree/base/ | grep iommus
-
强制绑定到 SMMU:
echo "1" > /sys/kernel/debug/iommu/arm-smmu/streamid
3.2 性能下降(TLB 未命中率高)
现象:使用 SMMU 后,外设吞吐量下降,perf 显示大量 TLB 未命中。
原因:
-
页表层级过多。
-
映射粒度太小(频繁 4KB 映射)。
-
TLB 缓存大小不足。
调试方法:
-
查看 TLB 统计:
cat /sys/kernel/debug/iommu/arm-smmu/tlb_stats
-
增大映射粒度:使用 2MB 大页映射。
-
调整页表层级:减少页表层级(如从 3 级降为 2 级)。
3.3 中断风暴 (SMMU 错误频繁触发)
现象:SMMU 错误中断频繁触发,CPU 占用高。
原因:
-
设备未正确初始化。
-
内存访问越界。
-
页表映射错误。
调试方法:
-
屏蔽错误中断:
# 临时屏蔽错误中断 devmem2 <base>+0x08 0x00
-
逐步启用设备:依次绑定设备,定位哪个设备触发错误。
-
检查映射一致性:确保 iommu_map/unmap 成对调用。
3.4 多设备冲突
现象:多个设备共享 SMMU,导致地址冲突或映射错误。
原因:
-
Stream ID 重复。
-
地址空间重叠。
-
Context Bank 分配冲突。
调试方法:
-
检查 Stream ID 分配:
cat /sys/kernel/debug/iommu/arm-smmu/streamid_list
-
分配独立的 Context Bank:
# 强制设备使用独立的 Context Bank echo <dev_name> > /sys/kernel/debug/iommu/arm-smmu/independent_cb
第四章 结合性能调试场景示例
场景:GPU 使用 SMMU 后,帧率从 60fps 下降到 30fps,perf 显示大量 TLB 未命中。
分析流程:
-
宏观层面(Memory 层):
-
perf top显示arm_smmu_map和arm_smmu_unmap占用 CPU 约 30%。 -
内存带宽正常。
-
-
SMMU 层(Device Drivers -> SMMU 层):
cat /sys/kernel/debug/iommu/arm-smmu/tlb_stats
发现 TLB 未命中率高达 80%。
-
GPU 层(GPU 控制器层):
cat /sys/kernel/debug/dri/0/panfrost/mm_fault
显示大量 4KB 粒度映射。
-
根本原因:
-
GPU 纹理数据被分割成大量 4KB 的小映射。
-
SMMU 频繁进行页表遍历,TLB 缓存未命中。
-
映射粒度过小,TLB 无法覆盖足够大的地址范围。
-
-
解决方案:
-
调整 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",设备无法工作。
原因:
-
电源域依赖的时钟未使能。
-
硬件上存在短路或电源故障。
-
上电流程中的状态位检查逻辑错误。
调试方法:
-
检查依赖时钟:
cat /sys/kernel/debug/clk/clk_summary | grep <domain_name>
-
读取电源域寄存器:
devmem2 <base>+<pwr_reg> # 查看当前电源控制位 devmem2 <base>+<pwr_st_reg> # 查看电源状态位
-
手动强制上电:
devmem2 <base>+<pwr_reg> 0x1 # 尝试强制上电
3.2 电源域无故断电
现象:工作正常的设备突然掉电,导致系统卡死或崩溃。
原因:
-
电源域驱动由于某种原因触发了
power_off。 -
热保护或电源管理策略误判。
-
硬件故障或电压跌落。
调试方法:
-
检查内核日志:
dmesg | grep "power off" | grep <domain>
-
检查硬件状态:
devmem2 <base>+<pwr_st_reg> # 查看电源状态
3.3 依赖关系配置错误
现象:某个域上电时,其依赖的域未上电,导致设备启动失败。
原因:
-
设备树中的
power-domains属性配置错误。 -
依赖链未正确解析。
调试方法:
-
检查设备树:
dtc -I fs /sys/firmware/devicetree/base/ | grep <domain>
-
查看电源域拓扑:
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary
3.4 电源域无法断电
现象:pm_genpd_power_off 调用后,设备仍然处于工作状态,功耗未下降。
原因:
-
设备未关闭其自身对电源域的使用计数。
-
电源域内还有活跃设备或时钟未关闭。
调试方法:
-
查看设备计数:
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep <domain>
-
强制断电:
echo "off" > /sys/class/power_supply/<domain>/state
第四章 结合性能调试场景示例
场景:GPU 在系统空闲时无法进入低功耗状态,pm_genpd_summary 显示 GPU 域始终上电。
分析流程:
-
宏观层面(图谱的 Power 层):
-
系统空闲时 CPU 进入空闲状态,但 GPU 域未掉电。
-
电池续航缩短约 20%。
-
-
Power Domain 层(图谱的 Power Domain 层):
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep gpu
显示 GPU 域状态为 "on",且 "usage count" 不为 0。
-
GPU 驱动层:
cat /sys/kernel/debug/dri/0/panfrost/power
显示 GPU 正在等待
submit完成。 -
根本原因:
-
用户空间应用程序在结束渲染后,未正确释放 GPU 资源。
-
GPU 驱动中的引用计数未归零,导致电源域无法断电。
-
-
解决方案:
-
增加用户空间的超时机制,强制回收资源。
-
在 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/,核心层次分为三层:
-
Devfreq 框架 (
drivers/devfreq/):提供动态频率调整和性能监控。 -
DDR 控制器驱动(本篇文章重点):具体的 SoC DDR 控制器驱动(如
rockchip-dmc.c)。 -
内存框架:管理内存的初始化、训练和带宽分配。
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 检测到不可纠正错误。
调试方法:
-
检查 ECC 状态:
devmem2 <base>+0x1C # DMC_ECC_STATUS
-
检查内存错误统计:
cat /sys/kernel/debug/dram/errors
-
降低内存频率:
echo 400 > /sys/devices/platform/soc/.../dmc/freq
-
压力测试:
# 使用 memtester 测试 memtester 512M 10
3.2 系统卡顿或响应慢
现象:系统响应迟缓,iostat 显示内存带宽高但 CPU 占用正常。
原因:
-
内存频率自动调节策略不合理。
-
带宽监控误判。
-
内存控制器配置错误。
调试方法:
-
查看当前频率:
cat /sys/devices/platform/soc/.../dmc/cur_freq
-
查看带宽使用率:
cat /sys/devices/platform/soc/.../dmc/bandwidth
-
调整 Devfreq 策略:
echo "userspace" > /sys/class/devfreq/dmc/governor echo 800 > /sys/class/devfreq/dmc/freq
-
增加频率切换阈值:
# 在驱动中调整 target 函数的阈值
3.3 低功耗模式异常
现象:系统进入睡眠后无法唤醒,或唤醒后内存数据丢失。
原因:
-
低功耗模式配置错误。
-
自刷新 (Self-Refresh) 未正确进入。
-
内存电源管理异常。
调试方法:
-
检查低功耗状态:
devmem2 <base>+0x14 # DMC_LOW_PWR
-
手动进入低功耗:
echo 1 > /sys/devices/platform/soc/.../dmc/low_power
-
检查内存控制器寄存器:
devmem2 <base>+0x04 # DMC_STATUS
3.4 ECC 频繁报错
现象:dmesg 中频繁出现 "ECC single-bit error detected"。
原因:
-
内存颗粒质量差或老化。
-
电源噪声过大。
-
内存时序配置过紧。
调试方法:
-
查看 ECC 统计:
cat /sys/kernel/debug/dram/ecc_stats
-
增加 ECC 纠正阈值:
# 在 DTS 中设置 ecc-strength = <4>;
-
更换内存颗粒。
第四章 结合性能调试场景示例
场景:系统进行高负载运算时,dmesg 频繁出现 "ECC single-bit error detected",系统偶尔崩溃。
分析流程:
-
宏观层面(图谱的 CPU 和 Memory 层):
-
top显示 CPU 负载正常,但内存负载高。 -
perf top显示rockchip_dmc_irq_handler占用 CPU 约 10%。
-
-
DMC 层(图谱的 Device Drivers -> DDR 控制器层):
cat /sys/kernel/debug/dram/ecc_stats
发现 ECC 错误计数随时间线性增长。
-
硬件层面(Memory 硬件层):
-
使用示波器检查 DDR 信号质量,发现时钟抖动过大。
-
测量电源电压,发现在高负载时电压有 10% 的跌落。
-
-
根本原因:
-
电源的稳压不足,在高负载时电压跌落,导致内存访问不可靠。
-
DDR 频率设置为 1600MHz,超出内存颗粒的稳定工作范围。
-
-
解决方案:
-
降低内存频率到 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/,核心层次分为三层:
-
Crypto 核心层 (
crypto/):提供通用的加密算法抽象,管理算法注册。 -
Crypto 控制器驱动(本篇文章重点):具体的 SoC 加密硬件驱动(如
rockchip-crypto.c)。 -
用户空间接口:通过
/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 模式未在硬件中实现)。
调试方法:
-
检查算法注册:
cat /proc/crypto | grep rockchip
-
测试算法支持:
# 使用 cryptsetup 测试 AES 加密 cryptsetup luksFormat --cipher aes-cbc-essiv:sha256 /dev/sda
-
查看日志:
dmesg | grep crypto
3.2 DMA 传输错误
现象:加密操作卡住,dmesg 出现 "DMA mapping error" 或 "Operation timeout"。
原因:
-
DMA 缓冲区未对齐到硬件要求(某些硬件需要 16 或 64 字节对齐)。
-
源数据长度不是 16 字节的倍数(AES 块大小要求)。
-
DMA 地址超过 32 位范围。
调试方法:
-
检查 DMA 对齐:
# 检查缓冲区地址 cat /sys/kernel/debug/dma/dma-* | grep crypto
-
调整数据长度:在驱动中填充数据到 16 字节倍数。
-
使用
perf跟踪 DMA 中断:perf record -e dma:* -a -- timeout 5 perf script | grep crypto
3.3 性能瓶颈(吞吐量低)
现象:openssl speed aes-256-cbc 测速结果远低于硬件理论值。
原因:
-
中断开销过高。
-
每次操作都经过 DMA 映射/取消映射。
-
未使用批量处理或请求队列。
调试方法:
-
查看中断频率:
cat /proc/interrupts | grep crypto
-
增加批处理大小:在驱动中合并多个请求。
-
使用
perf分析热点:perf record -e cycles -g openssl speed aes-256-cbc perf report
3.4 哈希结果错误
现象:使用 SHA 哈希得到的结果与软件实现不一致。
原因:
-
哈希状态未正确初始化。
-
数据长度处理错误(SHA 需要填充)。
-
字节序问题(大端/小端)。
调试方法:
-
对比软件计算结果:
# 使用 openssl 计算 echo -n "test" | openssl sha256
-
调试寄存器状态:
devmem2 <base>+0x10 # CRYPTO_IV 寄存器
-
检查初始化向量:确保每次哈希开始前清零。
第四章 结合性能调试场景示例
场景:在系统负载高时,加密操作偶尔失败,dmesg 出现 "AES operation timeout"。
分析流程:
-
宏观层面(图谱的 CPU 层):
-
top显示kworker/crypto进程占用 CPU 较高。 -
perf top显示rockchip_crypto_irq_handler占用 30% CPU。
-
-
Crypto 层(图谱的 Device Drivers -> Crypto Engine 层):
cat /proc/interrupts | grep crypto
发现中断触发频率很高(5000+ 次/秒)。
-
DMA 层(DMA 控制器层):
trace-cmd record -e dma:* -e crypto:* -a -- timeout 5 trace-cmd report | grep rockchip_crypto
发现 DMA 中断处理延迟超过 1ms。
-
根本原因:
-
每次加密操作都需要重新映射 DMA 缓冲区,DMA 映射和取消映射操作频繁。
-
加密请求频率高,中断处理无法及时完成所有请求。
-
中断处理程序在释放 DMA 映射时被较高优先级任务抢占。
-
-
解决方案:
-
实现请求队列,批量处理加密操作。
-
使用 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/,核心层次分为三层:
-
Pinctrl 核心层 (
drivers/pinctrl/core.c):提供 pin 控制、复用、配置的通用 API。 -
Pinctrl 控制器驱动(本篇文章重点):具体的 SoC Pin 控制器驱动(如
pinctrl-rockchip.c)。 -
Pinctrl 客户端驱动:使用
pinctrl_get/select_state配置引脚的外设驱动(如dw_i2c、dw_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-names与pinctrl-0配置错误。 -
引脚被 Bootloader 占用且未释放。
调试方法:
-
查看引脚使用情况:
cat /sys/kernel/debug/pinctrl/pinctrl-handles
-
查看引脚状态:
cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins
-
检查设备树配置:
dtc -I fs /sys/firmware/devicetree/base/ | grep <pin_name>
3.2 中断配置错误
现象:GPIO 中断无法触发,cat /proc/interrupts 无计数。
原因:
-
中断类型配置错误(边沿 vs 电平)。
-
GPIO 中断未使能。
-
引脚被配置为非中断功能。
调试方法:
-
检查 GPIO 中断配置:
devmem2 <base>+0x30 # GPIO_INTEN devmem2 <base>+0x38 # GPIO_INTTYPE_LEVEL devmem2 <base>+0x3C # GPIO_INTTYPE_EDGE
-
手动触发中断:
# 通过 devmem2 强制写入中断状态 devmem2 <base>+0x44 0x1
3.3 驱动强度导致信号异常
现象:I2C 或 SPI 设备通信不稳定,信号波形异常。
原因:
-
驱动强度过弱。
-
上拉电阻配置错误。
-
外部负载过大。
调试方法:
-
检查驱动强度:
devmem2 <base>+0x200 # 驱动强度寄存器
-
检查上拉配置:
devmem2 <base>+0x300 # 上拉寄存器
-
调整驱动强度:
# 通过 sysfs 调整 echo "1000" > /sys/class/pinctrl/rockchip-pinctrl/pinconf/set_drive
3.4 复位后引脚状态恢复失败
现象:系统复位后,某些外设引脚功能丢失。
原因:
-
Bootloader 未正确设置引脚状态。
-
内核中的 Pinctrl 配置未生效。
-
设备树中的
default状态缺失。
调试方法:
-
检查内核引脚的初始状态:
cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins
-
强制应用配置:
echo 1 > /sys/kernel/debug/pinctrl/rockchip-pinctrl/force_config
第四章 结合性能调试场景示例
场景:I2C 总线上的传感器无法访问,i2cdetect -y 0 无响应。
分析流程:
-
宏观层面(图谱的 Device Drivers -> I2C 层):
-
dmesg | grep i2c显示 "I2C bus 0 no response"。 -
perf top未显示异常。
-
-
Pinctrl 层(图谱的 Device Drivers -> Pinctrl 层):
cat /sys/kernel/debug/pinctrl/rockchip-pinctrl/pinmux-pins | grep i2c
发现 I2C 的 SCL 和 SDA 引脚被配置为 GPIO 而不是 I2C 功能。
-
设备树检查:
dtc -I fs /sys/firmware/devicetree/base/ | grep i2c
发现 I2C 节点的
pinctrl-names配置为 "default",但pinctrl-0指向的引脚组不存在。 -
根本原因:
-
设备树中 I2C 引脚组配置错误,导致内核无法正确设置引脚复用功能。
-
Bootloader 没有保留正确的 I2C 引脚配置。
-
-
解决方案:
-
修正设备树中 I2C 的
pinctrl-0配置,指向正确的引脚组。 -
重新编译设备树并烧录。
-
通过内核命令行强制应用配置。
-
第五章 与其他控制器的协同
| 控制器 | 协同方式 | 调试关键点 |
|---|---|---|
| GPIO | Pinctrl 通常集成 GPIO 功能 | 引脚中断、方向配置 |
| I2C/SPI/UART | 通过 Pinctrl 配置引脚功能 | 复用选择、驱动强度 |
| PWM | 通过 Pinctrl 配置 PWM 引脚 | 复用选择、上拉/下拉 |
| Interrupt Controller (GIC) | GPIO 中断通过 GIC 路由 | 中断类型、触发方式 |
| Reset Controller | 复位后引脚状态丢失 | 复位后重新配置 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)