11. 学习阶段总结与进阶指引
╔══════════════════════════════════════════════════════════════════════╗
║ 学习阶段与涵盖内容总结 ║
╠══════════════════════════════════════════════════════════════════════╣
║ ║
║ 阶段1: DMA基础 (入门) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 知识点 │ 代码位置 │ 难度 │ ║
║ ├───────────────────────────┼─────────────────────┼───────────┤ ║
║ │ 什么是DMA │ .h 文件开头注释 │ ⭐ │ ║
║ │ PIO vs DMA对比 │ oled_pio_transfer() │ ⭐ │ ║
║ │ DMA三种地址类型 │ .h 地址类型注释 │ ⭐⭐ │ ║
║ │ DMA掩码设置 │ dma_set_mask() │ ⭐⭐ │ ║
║ │ DMA方向 │ demo_dma_direction() │ ⭐⭐ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ 阶段2: DMA映射API (核心) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 知识点 │ 代码位置 │ 难度 │ ║
║ ├───────────────────────────┼─────────────────────┼───────────┤ ║
║ │ 一致性DMA分配/释放 │ dma_init_coherent() │ ⭐⭐ │ ║
║ │ 一致性DMA传输 │ transfer_coherent() │ ⭐⭐ │ ║
║ │ 流式DMA map/unmap │ transfer_streaming() │ ⭐⭐⭐ │ ║
║ │ DMA映射错误检查 │ dma_mapping_error() │ ⭐⭐ │ ║
║ │ is_dma_mapped标志 │ transfer_coherent() │ ⭐⭐⭐ │ ║
║ │ Scatter-Gather表 │ dma_init_sg() │ ⭐⭐⭐ │ ║
║ │ SG映射与传输 │ transfer_sg() │ ⭐⭐⭐⭐ │ ║
║ │ DMA Pool │ demo_dma_pool() │ ⭐⭐⭐ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ 阶段3: Cache一致性 (重要) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 知识点 │ 代码位置 │ 难度 │ ║
║ ├───────────────────────────┼─────────────────────┼───────────┤ ║
║ │ Cache flush/invalidate │ streaming传输注释 │ ⭐⭐⭐ │ ║
║ │ 所有权模型 │ streaming_reuse() │ ⭐⭐⭐ │ ║
║ │ sync_for_cpu/device │ streaming_reuse() │ ⭐⭐⭐⭐ │ ║
║ │ 方向与Cache操作关系 │ demo_dma_direction() │ ⭐⭐⭐ │ ║
║ │ 架构差异(x86 vs ARM) │ debug_dma_info() │ ⭐⭐⭐⭐ │ ║
║ │ Bounce Buffer │ demo_bounce_buffer() │ ⭐⭐⭐ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ 阶段4: DMA Engine (进阶) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 知识点 │ 代码位置 │ 难度 │ ║
║ ├───────────────────────────┼─────────────────────┼───────────┤ ║
║ │ DMA通道请求 │ dma_engine_transfer()│ ⭐⭐⭐ │ ║
║ │ Slave Config │ dma_engine_transfer()│ ⭐⭐⭐ │ ║
║ │ 描述符准备 │ dma_engine_transfer()│ ⭐⭐⭐⭐ │ ║
║ │ 提交与启动 │ dma_engine_transfer()│ ⭐⭐⭐ │ ║
║ │ 完成回调 │ oled_dma_callback() │ ⭐⭐⭐ │ ║
║ │ 超时处理与恢复 │ dma_engine_transfer()│ ⭐⭐⭐⭐ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
║ 阶段5: 高级技巧 (精通) ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 知识点 │ 代码位置 │ 难度 │ ║
║ ├───────────────────────────┼─────────────────────┼───────────┤ ║
║ │ 流式映射重用 │ streaming_reuse_*() │ ⭐⭐⭐⭐ │ ║
║ │ 乒乓缓冲区 │ demo_pingpong() │ ⭐⭐⭐⭐ │ ║
║ │ 异步SPI+DMA │ async_dma_transfer() │ ⭐⭐⭐⭐ │ ║
║ │ 异步乒乓DMA │ async_pingpong() │ ⭐⭐⭐⭐⭐│ ║
║ │ IOMMU原理 │ 注释块 │ ⭐⭐⭐⭐ │ ║
║ │ DMA内存屏障 │ 注释块 │ ⭐⭐⭐⭐⭐│ ║
║ │ DMA调试技巧 │ debug_dma_info() │ ⭐⭐⭐ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
║ ║
╚══════════════════════════════════════════════════════════════════════╝
12. DMA常见错误与排查指南
/*
* spi_oled_dma_errors.c
*
* 📚 DMA常见错误汇编与排查方法
*
* 这个文件列举了实际开发中最常见的DMA错误
* 每个错误都有"错误写法"和"正确写法"对照
*/
/* ============================================================
* 错误1: 使用栈上的缓冲区做DMA
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_example_stack(struct spi_oled_device *oled)
{
u8 buf[128]; /* 栈上分配! */
dma_addr_t dma;
memset(buf, 0xAA, sizeof(buf));
/*
* ❌ 问题:
* 1. 栈内存可能不在DMA可达的地址范围内
* 2. 栈内存可能没有对齐到cache line
* 3. 函数返回后栈会被回收,如果DMA还在进行 = 灾难!
* 4. 在某些架构上会导致cache一致性问题
*
* 可能的症状:
* - 有时正常工作,有时数据损坏(取决于栈地址)
* - 在x86上可能"偶然正确",移植到ARM后出bug
* - CONFIG_DMA_API_DEBUG会报告错误
*/
dma = dma_map_single(oled->dev, buf, sizeof(buf), DMA_TO_DEVICE);
/* ... 传输 ... */
dma_unmap_single(oled->dev, dma, sizeof(buf), DMA_TO_DEVICE);
return 0; /* 返回后buf被回收! */
}
/* ✅ 正确写法 */
static int dma_correct_example_heap(struct spi_oled_device *oled)
{
u8 *buf;
dma_addr_t dma;
int ret;
/* 使用kmalloc分配堆内存 */
buf = kmalloc(128, GFP_KERNEL | __GFP_DMA);
if (!buf)
return -ENOMEM;
memset(buf, 0xAA, 128);
dma = dma_map_single(oled->dev, buf, 128, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
/* ... 传输 ... */
dma_unmap_single(oled->dev, dma, 128, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ============================================================
* 错误2: 忘记检查DMA映射错误
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_no_check(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
/*
* ❌ 没有检查dma_mapping_error()!
*
* 如果映射失败:
* - dma可能是一个无效地址
* - 使用无效地址做DMA传输 = 数据损坏或系统崩溃
* - IOMMU系统上可能导致IOMMU fault
*
* ⚠️ 注意: dma_addr_t == 0 是合法地址!
* 不能用 if (dma == 0) 来检查错误
*/
/* 直接使用dma地址 - 危险! */
/* ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ✅ 正确写法 */
static int dma_correct_with_check(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
int ret = 0;
if (!buf)
return -ENOMEM;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
/* ✅ 使用dma_mapping_error()检查 */
if (dma_mapping_error(oled->dev, dma)) {
dev_err(oled->dev, "DMA mapping failed\n");
kfree(buf);
return -EIO;
}
/* 安全使用dma地址 */
/* ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return ret;
}
/* ============================================================
* 错误3: map和unmap不配对
* ============================================================ */
/* ❌ 错误写法: 忘记unmap */
static int dma_error_leak(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
/* ... 传输 ... */
/*
* ❌ 忘记调用dma_unmap_single()!
*
* 后果:
* - IOMMU映射泄漏 (IOMMU地址空间被占用)
* - 长期运行后IOMMU地址空间耗尽
* - DMA debug会报告 "DMA-API: device has pending DMA allocations"
* - 驱动卸载时内核警告
*/
kfree(buf);
return 0; /* 泄漏! */
}
/* ❌ 错误写法: 错误路径忘记unmap */
static int dma_error_leak_on_error(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
int ret;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
ret = some_operation();
if (ret) {
kfree(buf);
return ret; /* ❌ 忘记unmap就return了! */
}
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ✅ 正确写法: 使用goto统一清理 */
static int dma_correct_goto_cleanup(struct spi_oled_device *oled)
{
void *buf = NULL;
dma_addr_t dma = 0;
bool mapped = false;
int ret;
buf = kmalloc(1024, GFP_KERNEL);
if (!buf)
return -ENOMEM;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
ret = -EIO;
goto err_free_buf;
}
mapped = true;
ret = some_operation();
if (ret)
goto err_unmap; /* ✅ 跳转到统一清理 */
/* ... 正常传输 ... */
ret = 0;
err_unmap:
if (mapped)
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
err_free_buf:
kfree(buf);
return ret;
}
/* ============================================================
* 错误4: DMA方向不匹配
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_wrong_direction(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
/* 我们要发送数据到OLED (CPU -> 设备) */
memset(buf, 0xAA, 1024);
/*
* ❌ 使用了错误的方向!
* DMA_FROM_DEVICE 意思是"设备写到内存"
* 但我们实际是"内存写到设备"
*
* 错误的方向导致:
* - map时做了invalidate而不是flush
* - CPU写入的数据可能还在Cache中,没有flush到内存
* - DMA控制器从内存读到的是旧数据!
*
* 在ARM上: 数据损坏
* 在x86上: 可能偶然正确 (硬件Cache snooping)
*/
dma = dma_map_single(oled->dev, buf, 1024,
DMA_FROM_DEVICE); /* ❌ 应该是DMA_TO_DEVICE */
/* ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_FROM_DEVICE);
kfree(buf);
return 0;
}
/* ✅ 正确写法 */
static int dma_correct_direction(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
/* CPU写入数据 */
memset(buf, 0xAA, 1024);
/* ✅ 正确方向: CPU -> 设备 = DMA_TO_DEVICE */
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
/* map时会flush cache,确保内存中有最新数据 */
/* DMA控制器可以安全读取 */
/* ... 传输 ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ============================================================
* 错误5: 在设备拥有期间CPU访问缓冲区
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_ownership(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
memset(buf, 0xAA, 1024);
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
/* 从此刻起,设备拥有缓冲区 */
/*
* ❌ 在map之后修改缓冲区!
*
* 此时设备拥有缓冲区:
* - CPU的写入可能只到Cache,不到内存
* - DMA控制器看不到新写入的数据
* - 最终传输的是旧数据 + 新数据的混合 = 数据损坏
*
* 在ARM上:
* map时已经flush了cache
* CPU新写入的数据在Cache中
* DMA从内存读取的是flush之前的数据
*/
((u8 *)buf)[0] = 0xBB; /* ❌ 违反所有权! */
((u8 *)buf)[1] = 0xCC; /* ❌ 违反所有权! */
/* ... 传输 (数据可能不正确) ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ✅ 正确写法: 使用sync切换所有权 */
static int dma_correct_ownership(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
memset(buf, 0xAA, 1024);
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
/* 设备拥有 */
/* ✅ 先取回所有权 */
dma_sync_single_for_cpu(oled->dev, dma, 1024, DMA_TO_DEVICE);
/* 现在CPU拥有 */
/* ✅ CPU安全修改 */
((u8 *)buf)[0] = 0xBB;
((u8 *)buf)[1] = 0xCC;
/* ✅ 交还所有权给设备 */
dma_sync_single_for_device(oled->dev, dma, 1024, DMA_TO_DEVICE);
/* 设备拥有,Cache已flush */
/* ... 安全传输 ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE);
kfree(buf);
return 0;
}
/* ============================================================
* 错误6: SG表unmap时使用错误的nents
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_sg_nents(struct spi_oled_device *oled)
{
struct sg_table sgt;
int mapped_nents;
sg_alloc_table(&sgt, 8, GFP_KERNEL);
/* ... 设置sg条目 ... */
/* mapped_nents可能 < 8 (IOMMU合并) */
mapped_nents = dma_map_sg(oled->dev, sgt.sgl, 8, DMA_TO_DEVICE);
/* ... 传输 ... */
/*
* ❌ 使用mapped_nents而不是原始nents!
*
* dma_unmap_sg需要知道原始映射了多少条目
* 如果IOMMU合并了条目,mapped_nents < 8
* 使用mapped_nents会导致部分条目没有被unmap
* = IOMMU映射泄漏
*/
dma_unmap_sg(oled->dev, sgt.sgl, mapped_nents, DMA_TO_DEVICE); /* ❌ */
sg_free_table(&sgt);
return 0;
}
/* ✅ 正确写法 */
static int dma_correct_sg_nents(struct spi_oled_device *oled)
{
struct sg_table sgt;
int orig_nents = 8;
int mapped_nents;
sg_alloc_table(&sgt, orig_nents, GFP_KERNEL);
/* ... 设置sg条目 ... */
mapped_nents = dma_map_sg(oled->dev, sgt.sgl, orig_nents, DMA_TO_DEVICE);
if (mapped_nents == 0) {
sg_free_table(&sgt);
return -EIO;
}
/* 传输时使用mapped_nents遍历 */
/* for_each_sg(sgt.sgl, sg, mapped_nents, i) { ... } */
/* ✅ unmap时使用原始nents */
dma_unmap_sg(oled->dev, sgt.sgl, orig_nents, DMA_TO_DEVICE);
sg_free_table(&sgt);
return 0;
}
/* ============================================================
* 错误7: 在DMA传输完成前释放缓冲区
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_premature_free(struct spi_oled_device *oled)
{
void *buf = kmalloc(1024, GFP_KERNEL);
struct spi_transfer xfer = {0};
struct spi_message msg;
/* 配置异步传输 */
spi_message_init(&msg);
xfer.tx_buf = buf;
xfer.len = 1024;
spi_message_add_tail(&xfer, &msg);
/* 异步提交 */
spi_async(oled->spi, &msg);
/*
* ❌ spi_async立即返回,传输还在后台进行
* 此时释放buf = DMA正在访问被释放的内存!
*
* 后果:
* - 数据损坏
* - use-after-free
* - 内核oops或panic
* - 间歇性、难以复现的bug
*/
kfree(buf); /* ❌ DMA可能还在使用! */
return 0;
}
/* ✅ 正确写法: 在回调中释放 */
struct my_async_data {
void *buf;
struct completion done;
};
static void my_async_complete(void *context)
{
struct my_async_data *data = context;
/* DMA已完成,现在可以安全释放 */
complete(&data->done);
}
static int dma_correct_async_lifetime(struct spi_oled_device *oled)
{
struct my_async_data data;
struct spi_transfer xfer = {0};
struct spi_message msg;
unsigned long timeout;
data.buf = kmalloc(1024, GFP_KERNEL);
if (!data.buf)
return -ENOMEM;
init_completion(&data.done);
spi_message_init(&msg);
xfer.tx_buf = data.buf;
xfer.len = 1024;
spi_message_add_tail(&xfer, &msg);
msg.complete = my_async_complete;
msg.context = &data;
spi_async(oled->spi, &msg);
/* ✅ 等待传输完成后再释放 */
timeout = wait_for_completion_timeout(&data.done, msecs_to_jiffies(5000));
if (timeout == 0) {
dev_err(oled->dev, "传输超时\n");
/* 即使超时也不能立即free,需要确保DMA停止 */
}
/* ✅ 传输确认完成后释放 */
kfree(data.buf);
return 0;
}
/* ============================================================
* 错误8: coherent和streaming混用
* ============================================================ */
/* ❌ 错误写法 */
static int dma_error_mixed_api(struct spi_oled_device *oled)
{
void *vaddr;
dma_addr_t dma_addr;
/* 用coherent API分配 */
vaddr = dma_alloc_coherent(oled->dev, 1024, &dma_addr, GFP_KERNEL);
/*
* ❌ 用streaming API来unmap coherent内存!
*
* dma_alloc_coherent和dma_map_single使用不同的内部机制
* - coherent: 分配+映射一体化,使用non-cacheable映射
* - streaming: 只做映射,保留cache属性
*
* 混用会导致:
* - 内存泄漏(coherent的页没有被正确释放)
* - IOMMU状态不一致
* - 内核BUG或oops
*/
dma_unmap_single(oled->dev, dma_addr, 1024, DMA_TO_DEVICE); /* ❌ */
return 0;
}
/* ✅ 正确写法: API配对使用 */
static int dma_correct_matched_api(struct spi_oled_device *oled)
{
/* 方式1: coherent API配对 */
{
void *vaddr;
dma_addr_t dma_addr;
vaddr = dma_alloc_coherent(oled->dev, 1024, &dma_addr, GFP_KERNEL);
if (!vaddr) return -ENOMEM;
/* ... */
dma_free_coherent(oled->dev, 1024, vaddr, dma_addr); /* ✅ */
}
/* 方式2: streaming API配对 */
{
void *buf = kmalloc(1024, GFP_KERNEL);
dma_addr_t dma;
if (!buf) return -ENOMEM;
dma = dma_map_single(oled->dev, buf, 1024, DMA_TO_DEVICE);
if (dma_mapping_error(oled->dev, dma)) {
kfree(buf);
return -EIO;
}
/* ... */
dma_unmap_single(oled->dev, dma, 1024, DMA_TO_DEVICE); /* ✅ */
kfree(buf);
}
return 0;
}
/*
* 📚 错误排查工具总结
*
* 1. CONFIG_DMA_API_DEBUG (编译时启用)
* - 跟踪所有DMA映射操作
* - 检测泄漏、双重释放、方向错误
* - 性能有影响,只在调试时使用
*
* 启用方法:
* 内核配置: CONFIG_DMA_API_DEBUG=y
* 启动参数: dma_debug=on dma_debug_entries=65536
*
* 查看信息:
* /sys/kernel/debug/dma-api/errors - 错误信息
* /sys/kernel/debug/dma-api/dump - 所有活跃映射
* /sys/kernel/debug/dma-api/num_errors - 错误计数
* /sys/kernel/debug/dma-api/num_free - 空闲跟踪条目
* /sys/kernel/debug/dma-api/driver_filter - 过滤特定驱动
*
* 2. ftrace DMA跟踪
* - 跟踪DMA API调用的时间和参数
*
* echo 1 > /sys/kernel/debug/tracing/events/dma/enable
* cat /sys/kernel/debug/tracing/trace
*
* 3. dynamic debug (运行时启用调试打印)
* echo 'file spi_oled_dma.c +p' > /sys/kernel/debug/dynamic_debug/control
* echo 'module spi_oled_dma +p' > /sys/kernel/debug/dynamic_debug/control
*
* 4. KASAN (Kernel Address SANitizer)
* - 检测use-after-free、out-of-bounds等内存错误
* - CONFIG_KASAN=y
* - DMA缓冲区的越界访问也能检测到
*
* 5. lockdep (Lock Dependency Checker)
* - 检测死锁和不正确的锁使用
* - 例如在中断上下文中使用mutex(DMA回调中常见错误)
* - CONFIG_LOCKDEP=y
*/
/* ============================================================
* 错误9: 中断上下文中做不允许的操作
* ============================================================ */
/* ❌ 错误写法: DMA回调中使用可睡眠的API */
static void dma_error_callback_sleep(void *data)
{
struct spi_oled_device *oled = data;
void *buf;
/*
* ❌ DMA完成回调通常在中断/softirq上下文中
* 以下操作都是不允许的:
*/
/* ❌ GFP_KERNEL可能睡眠 */
buf = kmalloc(1024, GFP_KERNEL);
/* ❌ mutex可能睡眠 */
mutex_lock(&oled->buf_lock);
mutex_unlock(&oled->buf_lock);
/* ❌ msleep会睡眠 */
msleep(10);
/* ❌ copy_to_user需要进程上下文 */
/* copy_to_user(ubuf, kbuf, size); */
/*
* 后果:
* - BUG: scheduling while atomic
* - 系统死锁
* - 内核panic
* - lockdep警告
*/
if (buf) kfree(buf);
}
/* ✅ 正确写法: 回调中只做最小工作,复杂操作延迟到工作队列 */
struct dma_work_data {
struct work_struct work;
struct spi_oled_device *oled;
int status;
};
static void dma_deferred_work(struct work_struct *work)
{
struct dma_work_data *wd = container_of(work, struct dma_work_data, work);
struct spi_oled_device *oled = wd->oled;
/* ✅ 在工作队列的进程上下文中,可以安全做以下操作 */
mutex_lock(&oled->buf_lock);
if (wd->status == 0) {
/* 处理传输成功 */
dev_dbg(oled->dev, "DMA传输成功,执行后续处理\n");
} else {
/* 处理传输失败 */
dev_err(oled->dev, "DMA传输失败: %d\n", wd->status);
}
mutex_unlock(&oled->buf_lock);
kfree(wd);
}
static void dma_correct_callback(void *data)
{
struct spi_oled_device *oled = data;
struct dma_work_data *wd;
/* ✅ 在中断上下文中只做最小工作 */
/* ✅ GFP_ATOMIC不会睡眠 */
wd = kmalloc(sizeof(*wd), GFP_ATOMIC);
if (wd) {
INIT_WORK(&wd->work, dma_deferred_work);
wd->oled = oled;
wd->status = 0;
/* ✅ schedule_work在任何上下文都安全 */
schedule_work(&wd->work);
}
/* ✅ complete在任何上下文都安全 */
complete(&oled->dma_completion);
/* ✅ spin_lock在中断上下文中安全 */
spin_lock(&oled->stats_lock);
oled->stats.dma_transfers++;
spin_unlock(&oled->stats_lock);
}
/* ============================================================
* 错误10: DMA和Cache Line共享问题
* ============================================================ */
/*
* 📚 Cache Line Sharing (伪共享) 问题:
*
* 现代CPU的Cache以"Cache Line"为单位操作
* 典型Cache Line大小: 32字节(ARM) 或 64字节(x86)
*
* 问题场景:
* 同一个Cache Line中有两个变量:
* - 变量A: CPU频繁访问
* - 变量B: DMA缓冲区的一部分
*
* ┌─────────────────────Cache Line(64B)─────────────────────┐
* │ 变量A (CPU用) │ 变量B (DMA用) │
* └─────────────────────────────────────────────────────────┘
*
* 当DMA invalidate这个Cache Line时:
* - 变量A的缓存也被invalidate了!
* - CPU下次访问变量A需要从内存重新加载
* - 如果CPU刚写了变量A但还没flush,数据丢失!
*
* 解决方法:
* 1. DMA缓冲区大小对齐到Cache Line
* 2. DMA缓冲区地址对齐到Cache Line
* 3. DMA缓冲区不与其他数据共享Cache Line
*/
/* ❌ 错误写法: DMA缓冲区可能与其他数据共享Cache Line */
struct bad_layout {
u32 important_counter; /* CPU频繁访问 */
u8 dma_buf[100]; /* DMA缓冲区 */
u32 another_variable; /* CPU频繁访问 */
/*
* ❌ dma_buf与important_counter和another_variable
* 可能在同一个Cache Line中
* DMA操作dma_buf时会影响这些变量的Cache状态
*/
};
/* ✅ 正确写法: 使用对齐确保DMA缓冲区独占Cache Line */
struct good_layout {
u32 important_counter;
u32 another_variable;
/*
* ✅ DMA缓冲区独立分配,不与其他数据共享Cache Line
* 使用____cacheline_aligned确保对齐
*/
u8 dma_buf[128] ____cacheline_aligned;
};
/* ✅ 更好的写法: 动态分配DMA缓冲区 */
struct best_layout {
u32 important_counter;
u32 another_variable;
/* DMA缓冲区用kmalloc或dma_alloc_coherent单独分配 */
u8 *dma_buf; /* 指向独立分配的内存 */
dma_addr_t dma_addr;
};
static int init_best_layout(struct device *dev, struct best_layout *layout)
{
/*
* ✅ kmalloc保证返回的地址至少对齐到ARCH_KMALLOC_MINALIGN
* 通常 >= Cache Line大小
* 如果需要更大的对齐,使用 kmalloc_aligned() 或 dma_alloc_coherent()
*/
layout->dma_buf = kmalloc(128, GFP_KERNEL | __GFP_DMA);
if (!layout->dma_buf)
return -ENOMEM;
/* 或使用dma_alloc_coherent,它保证页对齐 */
/* layout->dma_buf = dma_alloc_coherent(dev, 128,
* &layout->dma_addr, GFP_KERNEL); */
return 0;
}
13. DMA学习实验补充:perf/ftrace 跟踪DMA操作
#!/bin/bash
#
# scripts/trace_dma.sh - 使用ftrace跟踪DMA操作
#
# 📚 ftrace是Linux内核内置的跟踪框架
# 可以跟踪DMA API的调用,帮助理解DMA工作流程
#
# 用法: sudo ./scripts/trace_dma.sh [start|stop|show|compare]
#
TRACE_DIR="/sys/kernel/debug/tracing"
TRACE_LOG="/tmp/dma_trace.log"
# 检查权限
check_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "请使用sudo运行"
exit 1
fi
}
# 启动DMA跟踪
start_trace() {
echo "📚 启动DMA API跟踪..."
# 清除旧的跟踪数据
echo > "$TRACE_DIR/trace"
# 启用函数跟踪器
echo function > "$TRACE_DIR/current_tracer"
# 设置要跟踪的DMA相关函数
cat > "$TRACE_DIR/set_ftrace_filter" << 'EOF'
dma_alloc_coherent
dma_free_coherent
dma_map_single_attrs
dma_unmap_single_attrs
dma_map_sg_attrs
dma_unmap_sg_attrs
dma_sync_single_for_cpu
dma_sync_single_for_device
dma_map_page_attrs
dma_mapping_error
EOF
# 也可以跟踪SPI相关函数
cat >> "$TRACE_DIR/set_ftrace_filter" << 'EOF'
spi_sync
spi_async
spi_finalize_current_message
EOF
# 启用函数图跟踪(显示调用关系和耗时)
echo function_graph > "$TRACE_DIR/current_tracer"
# 设置跟踪深度
echo 6 > "$TRACE_DIR/max_graph_depth"
# 启用时间戳
echo 1 > "$TRACE_DIR/options/funcgraph-duration"
echo 1 > "$TRACE_DIR/options/funcgraph-overhead"
# 只跟踪我们的驱动模块(如果已加载)
if lsmod | grep -q spi_oled_dma; then
# 获取驱动的PID范围(如果有工作线程)
echo " 过滤驱动相关的跟踪事件"
fi
# 启动跟踪
echo 1 > "$TRACE_DIR/tracing_on"
echo "✅ 跟踪已启动"
echo " 执行DMA操作后,运行: $0 show"
echo " 停止跟踪: $0 stop"
}
# 停止跟踪
stop_trace() {
echo "停止跟踪..."
echo 0 > "$TRACE_DIR/tracing_on"
echo nop > "$TRACE_DIR/current_tracer"
echo > "$TRACE_DIR/set_ftrace_filter"
echo "✅ 跟踪已停止"
}
# 显示跟踪结果
show_trace() {
echo ""
echo "╔══════════════════════════════════════════════════════╗"
echo "║ DMA API 跟踪结果 ║"
echo "╚══════════════════════════════════════════════════════╝"
echo ""
# 保存到文件
cat "$TRACE_DIR/trace" > "$TRACE_LOG"
# 显示跟踪内容
echo "📋 DMA函数调用序列:"
echo "────────────────────────────────────────────────"
grep -E "dma_|spi_" "$TRACE_LOG" | head -100
echo ""
echo "📊 DMA函数调用统计:"
echo "────────────────────────────────────────────────"
echo " dma_alloc_coherent: $(grep -c 'dma_alloc_coherent' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_free_coherent: $(grep -c 'dma_free_coherent' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_map_single: $(grep -c 'dma_map_single' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_unmap_single: $(grep -c 'dma_unmap_single' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_map_sg: $(grep -c 'dma_map_sg' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_unmap_sg: $(grep -c 'dma_unmap_sg' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_sync_single_for_cpu: $(grep -c 'sync_single_for_cpu' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " dma_sync_single_for_device:$(grep -c 'sync_single_for_device' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " spi_sync: $(grep -c 'spi_sync' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo " spi_async: $(grep -c 'spi_async' "$TRACE_LOG" 2>/dev/null || echo 0) 次"
echo ""
echo "📊 配对检查 (map/unmap应该相等):"
echo "────────────────────────────────────────────────"
local map_single=$(grep -c 'dma_map_single' "$TRACE_LOG" 2>/dev/null || echo 0)
local unmap_single=$(grep -c 'dma_unmap_single' "$TRACE_LOG" 2>/dev/null || echo 0)
local map_sg=$(grep -c 'dma_map_sg' "$TRACE_LOG" 2>/dev/null || echo 0)
local unmap_sg=$(grep -c 'dma_unmap_sg' "$TRACE_LOG" 2>/dev/null || echo 0)
local alloc=$(grep -c 'dma_alloc_coherent' "$TRACE_LOG" 2>/dev/null || echo 0)
local free=$(grep -c 'dma_free_coherent' "$TRACE_LOG" 2>/dev/null || echo 0)
if [ "$map_single" -eq "$unmap_single" ]; then
echo " ✅ map_single ($map_single) == unmap_single ($unmap_single)"
else
echo " ❌ map_single ($map_single) != unmap_single ($unmap_single) - 可能泄漏!"
fi
if [ "$map_sg" -eq "$unmap_sg" ]; then
echo " ✅ map_sg ($map_sg) == unmap_sg ($unmap_sg)"
else
echo " ❌ map_sg ($map_sg) != unmap_sg ($unmap_sg) - 可能泄漏!"
fi
if [ "$alloc" -ge "$free" ]; then
local pending=$((alloc - free))
echo " ℹ️ alloc ($alloc) - free ($free) = $pending 个活跃coherent映射"
if [ $pending -gt 10 ]; then
echo " ⚠️ 活跃映射较多,注意是否有泄漏"
fi
fi
echo ""
echo "完整跟踪日志已保存到: $TRACE_LOG"
echo "查看: less $TRACE_LOG"
}
# 对比不同DMA模式的函数调用
compare_modes() {
echo ""
echo "╔══════════════════════════════════════════════════════╗"
echo "║ DMA模式函数调用对比 ║"
echo "╚══════════════════════════════════════════════════════╝"
echo ""
local sysfs=$(find /sys/devices -name 'dma_mode' -path '*spi*' 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
if [ -z "$sysfs" ]; then
echo "❌ 未找到驱动sysfs,请确保驱动已加载"
return 1
fi
local modes=("0:Coherent" "1:Streaming" "2:Scatter-Gather" "3:PIO")
for mode_info in "${modes[@]}"; do
local mode="${mode_info%%:*}"
local name="${mode_info##*:}"
echo "═══ 模式 $mode: $name ═══"
# 切换模式
echo "$mode" > "$sysfs/dma_mode"
sleep 1
# 清除跟踪
echo > "$TRACE_DIR/trace"
echo 1 > "$TRACE_DIR/tracing_on"
# 触发一次刷新
echo 1 > "$sysfs/refresh" 2>/dev/null
# 等待完成
sleep 1
echo 0 > "$TRACE_DIR/tracing_on"
# 分析
local trace_content=$(cat "$TRACE_DIR/trace")
echo " DMA API调用:"
echo "$trace_content" | grep -E "dma_" | while read -r line; do
echo " $line"
done | head -15
local total_dma=$(echo "$trace_content" | grep -c "dma_" 2>/dev/null || echo 0)
echo " 总DMA API调用次数: $total_dma"
echo ""
done
echo "📚 观察要点:"
echo " - Coherent模式: 初始化时有alloc,传输时无map/unmap"
echo " - Streaming模式: 每次传输都有map/unmap"
echo " - SG模式: 有map_sg/unmap_sg调用"
echo " - PIO模式: 应该没有DMA API调用"
}
# 主程序
check_root
case "${1:-help}" in
start)
start_trace
;;
stop)
stop_trace
;;
show)
show_trace
;;
compare)
start_trace
compare_modes
stop_trace
;;
help|*)
echo ""
echo "DMA跟踪工具"
echo ""
echo "用法: sudo $0 <命令>"
echo ""
echo "命令:"
echo " start - 开始跟踪DMA API调用"
echo " stop - 停止跟踪"
echo " show - 显示跟踪结果和统计"
echo " compare - 对比不同DMA模式的API调用"
echo ""
echo "典型工作流:"
echo " sudo $0 start # 开始跟踪"
echo " # 执行一些OLED操作..."
echo " echo 1 > /sys/.../refresh"
echo " sudo $0 show # 查看结果"
echo " sudo $0 stop # 停止跟踪"
echo ""
;;
esac