Linux DMA(五)

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
posted @ 2026-02-26 16:23  21code  阅读(1)  评论(0)    收藏  举报