Linux DMA(一)

SPI OLED DMA 驱动程序 - 学习DMA概念

0. 目录结构

spi_oled_dma/
├── spi_oled_dma.c # 主驱动程序
├── spi_oled_dma.h # 头文件
├── spi_oled_fb.c # Framebuffer接口
├── Makefile # 编译文件
├── dts/
│ └── spi_oled_overlay.dts # 设备树
└── test/
└── test_oled.c # 用户空间测试程序

╔══════════════════════════════════════════════════════════════════════╗
║                    文件与知识点对照索引                              ║
╠══════════════════════════════════════════════════════════════════════╣
║                                                                      ║
║  spi_oled_dma.h                                                      ║
║  ├── DMA基础概念总述 ..................... 文件开头注释               ║
║  ├── DMA缓冲区结构说明 ................... struct oled_dma_buf       ║
║  ├── 一致性vs流式映射对比 ................ 结构体前注释              ║
║  └── DMA统计结构 ........................ struct oled_dma_stats      ║
║                                                                      ║
║  spi_oled_dma.c                                                      ║
║  ├── 第1部分: SPI基础通信                                            ║
║  │   ├── PIO传输原理 .................... oled_write_data_pio()     ║
║  │   └── SPI命令/数据区分 ............... oled_write_cmd()          ║
║  │                                                                   ║
║  ├── 第2部分: DMA初始化                                              ║
║  │   ├── DMA掩码设置 .................... dma_set_mask_and_coherent ║
║  │   ├── 一致性DMA分配 .................. dma_alloc_coherent()      ║
║  │   ├── 流式DMA内存要求 ................ kmalloc + __GFP_DMA      ║
║  │   ├── SG表创建 ....................... sg_alloc_table()          ║
║  │   └── DMA资源释放 .................... oled_dma_cleanup()        ║
║  │                                                                   ║
║  ├── 第3部分: DMA数据传输 (核心!)                                    ║
║  │   ├── 一致性DMA传输流程 .............. oled_dma_transfer_coherent║
║  │   │   └── is_dma_mapped标志 ......... xfer.is_dma_mapped        ║
║  │   ├── 流式DMA完整流程 ................ oled_dma_transfer_streaming║
║  │   │   ├── dma_map_single详解 ........ 步骤2                     ║
║  │   │   ├── dma_mapping_error检查 ...... 步骤2之后               ║
║  │   │   ├── Cache一致性图解 ........... 函数前注释                ║
║  │   │   ├── sync_for_cpu/device ....... 步骤间注释               ║
║  │   │   └── dma_unmap_single .......... 步骤5                     ║
║  │   ├── SG DMA传输 .................... oled_dma_transfer_sg()     ║
║  │   │   ├── dma_map_sg详解 ........... 步骤1                      ║
║  │   │   ├── IOMMU条目合并 ............ 步骤1注释                  ║
║  │   │   ├── sg_dma_address vs sg_phys . 步骤2                     ║
║  │   │   └── dma_unmap_sg注意事项 ...... 步骤4                     ║
║  │   └── PIO vs DMA对比分析 ............ oled_pio_transfer()       ║
║  │                                                                   ║
║  ├── 第6部分: sysfs调试接口                                          ║
║  │   ├── DMA统计查看 .................... dma_stats_show()          ║
║  │   ├── DMA模式动态切换 ................ dma_mode_store()          ║
║  │   ├── DMA地址映射查看 ................ dma_addresses_show()      ║
║  │   └── DMA教程输出 .................... dma_tutorial_show()       ║
║  │                                                                   ║
║  ├── 第8部分: DMA Engine API                                         ║
║  │   ├── DMA Engine完整流程 ............ oled_dma_engine_transfer() ║
║  │   │   ├── dma_request_chan ........... 步骤1                     ║
║  │   │   ├── dmaengine_slave_config .... 步骤2                     ║
║  │   │   ├── dmaengine_prep_slave_single 步骤4                     ║
║  │   │   ├── dmaengine_submit .......... 步骤6                     ║
║  │   │   ├── dma_async_issue_pending ... 步骤7                     ║
║  │   │   └── wait_for_completion ....... 步骤8                     ║
║  │   ├── DMA Pool演示 .................. oled_demo_dma_pool()       ║
║  │   ├── Bounce Buffer概念 ............. oled_demo_bounce_buffer()  ║
║  │   └── DMA方向与Cache关系 ............ oled_demo_dma_direction()  ║
║  │                                                                   ║
║  └── 第10部分: 探测与移除                                            ║
║      ├── 资源初始化顺序 ................. spi_oled_probe()          ║
║      └── 资源释放顺序 ................... spi_oled_remove()         ║
║                                                                      ║
║  补充文件:                                                           ║
║  ├── 流式DMA映射重用 .................... streaming_reuse_*         ║
║  │   ├── 持久映射初始化 ................ streaming_reuse_init()    ║
║  │   ├── 所有权状态机 .................. streaming_reuse_transfer() ║
║  │   └── sync_for_cpu/device详解 ....... 步骤1/步骤3               ║
║  ├── 乒乓缓冲区 ........................ oled_demo_pingpong()       ║
║  ├── 异步SPI传输 ........................ oled_async_dma_transfer() ║
║  │   ├── spi_async vs spi_sync ......... 函数前注释               ║
║  │   └── 完成回调注意事项 .............. async_transfer_complete() ║
║  ├── 异步乒乓DMA ........................ oled_async_pingpong()     ║
║  ├── IOMMU与DMA关系 .................... 补充注释块               ║
║  ├── DMA屏障与内存序 .................... 补充注释块               ║
║  ├── DMA与中断时序 ...................... 补充注释块               ║
║  └── DMA调试技巧 ........................ oled_debug_dma_info()     ║
║                                                                      ║
║  test/test_oled.c                                                    ║
║  ├── Framebuffer写入测试 ................ test_pattern_*            ║
║  ├── DMA模式性能对比 .................... benchmark_dma_mode()      ║
║  └── sysfs接口使用示例 .................. read_sysfs/write_sysfs    ║
║                                                                      ║
║  scripts/run_test.sh                                                 ║
║  ├── 自动化测试流程 .................... test_*系列函数            ║
║  ├── DMA系统环境检查 .................... test_debug()              ║
║  └── DMA压力测试 ........................ test_stress()             ║
║                                                                      ║
╚══════════════════════════════════════════════════════════════════════╝

1. 头文件 (spi_oled_dma.h)

/*
 * spi_oled_dma.h - SPI OLED DMA驱动头文件
 *
 * 用于学习Linux内核DMA概念的教学驱动
 * 支持SSD1306 OLED显示屏 (128x64)
 */

#ifndef _SPI_OLED_DMA_H_
#define _SPI_OLED_DMA_H_

#include <linux/types.h>
#include <linux/spi/spi.h>
#include <linux/fb.h>
#include <linux/gpio/consumer.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>

/* ============================================================
 * 📚 DMA基础概念说明
 * ============================================================
 *
 * DMA (Direct Memory Access) 直接内存访问:
 *
 * 传统方式 (PIO - Programmed I/O):
 *   CPU从内存读数据 -> CPU写到SPI控制器 -> 重复...
 *   CPU全程参与,无法做其他事情
 *
 * DMA方式:
 *   CPU告诉DMA控制器: "从这个地址开始,搬这么多数据到SPI控制器"
 *   DMA控制器自动搬运数据,CPU可以去做别的事情
 *   搬运完成后,DMA控制器通过中断通知CPU
 *
 * 关键概念:
 *   1. DMA缓冲区  - 一块特殊分配的内存,CPU和DMA控制器都能访问
 *   2. 总线地址   - DMA控制器看到的内存地址(可能与CPU看到的不同)
 *   3. DMA通道    - DMA控制器中的一条数据通路
 *   4. DMA描述符  - 描述一次DMA传输的参数(源地址、目的地址、长度等)
 *   5. scatter-gather - 将多个不连续的内存块在一次DMA操作中传输
 *
 * 内存地址类型:
 *   虚拟地址 (Virtual Address)  - CPU使用的地址
 *   物理地址 (Physical Address) - 实际内存芯片上的地址
 *   总线地址 (Bus/DMA Address)  - DMA控制器使用的地址
 *   在简单系统中,物理地址 == 总线地址
 *   在有IOMMU的系统中,它们可能不同
 *
 * DMA映射类型:
 *   一致性映射 (Coherent/Consistent) - 硬件保证CPU和设备看到相同数据
 *   流式映射   (Streaming)           - 需要显式同步操作
 */

/* SSD1306 OLED 显示参数 */
#define OLED_WIDTH              128
#define OLED_HEIGHT             64
#define OLED_PAGES              (OLED_HEIGHT / 8)  /* 8页,每页8行像素 */
#define OLED_BUF_SIZE           (OLED_WIDTH * OLED_PAGES)  /* 1024字节 */

/* SSD1306 命令定义 */
#define SSD1306_SET_LOW_COLUMN          0x00
#define SSD1306_SET_HIGH_COLUMN         0x10
#define SSD1306_SET_MEMORY_MODE         0x20
#define SSD1306_SET_COL_ADDR            0x21
#define SSD1306_SET_PAGE_ADDR           0x22
#define SSD1306_SET_START_LINE          0x40
#define SSD1306_SET_CONTRAST            0x81
#define SSD1306_CHARGE_PUMP             0x8D
#define SSD1306_SET_SEG_REMAP           0xA1
#define SSD1306_DISPLAY_ALL_ON_RESUME   0xA4
#define SSD1306_SET_NORMAL_DISPLAY      0xA6
#define SSD1306_SET_INVERSE_DISPLAY     0xA7
#define SSD1306_SET_MUX_RATIO           0xA8
#define SSD1306_DISPLAY_OFF             0xAE
#define SSD1306_DISPLAY_ON              0xAF
#define SSD1306_SET_COM_SCAN_DEC        0xC8
#define SSD1306_SET_DISPLAY_OFFSET      0xD3
#define SSD1306_SET_DISPLAY_CLOCK_DIV   0xD5
#define SSD1306_SET_PRECHARGE           0xD9
#define SSD1306_SET_COM_PINS            0xDA
#define SSD1306_SET_VCOM_DETECT         0xDB

/* DMA传输模式 */
enum oled_dma_mode {
    DMA_MODE_COHERENT,      /* 一致性DMA映射 */
    DMA_MODE_STREAMING,     /* 流式DMA映射 */
    DMA_MODE_SG,            /* Scatter-Gather DMA */
    DMA_MODE_PIO,           /* 无DMA,CPU直接传输(对比用) */
};

/*
 * ============================================================
 * 📚 DMA缓冲区结构说明
 * ============================================================
 *
 * 一致性DMA缓冲区 (Coherent DMA Buffer):
 *   分配: dma_alloc_coherent()
 *   释放: dma_free_coherent()
 *   特点: 
 *     - CPU和设备始终看到一致的数据(硬件保证cache一致性)
 *     - 不需要手动同步
 *     - 可能比较慢(通常映射为non-cacheable)
 *     - 适用于频繁被CPU和设备同时访问的缓冲区
 *   返回:
 *     - 虚拟地址(CPU使用)
 *     - DMA地址(设备使用)
 *
 * 流式DMA映射 (Streaming DMA Mapping):
 *   映射: dma_map_single()
 *   取消: dma_unmap_single()
 *   同步: dma_sync_single_for_device() / dma_sync_single_for_cpu()
 *   特点:
 *     - 可以映射普通kmalloc内存
 *     - 需要指定方向: DMA_TO_DEVICE / DMA_FROM_DEVICE / DMA_BIDIRECTIONAL
 *     - 需要手动同步(确保cache一致性)
 *     - 性能通常更好(内存可以是cacheable的)
 *     - 适用于一次性或间歇性的DMA传输
 */

/* DMA缓冲区信息 */
struct oled_dma_buf {
    void *vaddr;            /* CPU虚拟地址 */
    dma_addr_t dma_addr;    /* DMA总线地址 */
    size_t size;            /* 缓冲区大小 */
    bool is_coherent;       /* 是否为一致性映射 */
};

/* Scatter-Gather表项 */
struct oled_sg_entry {
    struct scatterlist sg;  /* 内核sg结构 */
    void *vaddr;            /* 该段的虚拟地址 */
    size_t size;            /* 该段的大小 */
};

/* 
 * DMA统计信息 - 用于学习和调试 
 * 通过sysfs导出,方便观察DMA行为
 */
struct oled_dma_stats {
    u64 dma_transfers;          /* DMA传输总次数 */
    u64 dma_bytes;              /* DMA传输总字节数 */
    u64 dma_errors;             /* DMA错误次数 */
    u64 pio_transfers;          /* PIO传输次数(对比) */
    u64 avg_transfer_time_ns;   /* 平均传输时间(纳秒) */
    ktime_t last_transfer_start;/* 上次传输开始时间 */
    ktime_t last_transfer_end;  /* 上次传输结束时间 */
};

/* 主设备结构体 */
struct spi_oled_device {
    /* SPI相关 */
    struct spi_device *spi;
    struct device *dev;

    /* GPIO引脚 */
    struct gpio_desc *dc_gpio;      /* 数据/命令选择引脚: 0=命令, 1=数据 */
    struct gpio_desc *reset_gpio;   /* 复位引脚 */

    /* Framebuffer相关 */
    struct fb_info *fb_info;
    u8 *fb_vmem;                    /* framebuffer显存(CPU格式: 每像素1bit) */
    size_t fb_vmem_size;

    /* ====== DMA相关 ====== */
    
    /* 
     * 一致性DMA缓冲区
     * 用于存放要发送到OLED的显示数据
     * CPU写入数据后,DMA控制器可以直接读取,无需额外同步
     */
    struct oled_dma_buf coherent_buf;

    /*
     * 流式DMA缓冲区
     * 普通内存 + DMA映射
     * 需要在传输前后进行同步操作
     */
    struct oled_dma_buf streaming_buf;

    /*
     * Scatter-Gather DMA
     * 将framebuffer分成多个不连续的块传输
     */
    struct sg_table sg_table;
    int sg_nents;                   /* SG表项数量 */

    /*
     * DMA通道
     * SPI控制器通常有自己的DMA通道
     * 这里我们也可以请求独立的DMA通道来学习
     */
    struct dma_chan *dma_chan_tx;    /* 发送DMA通道 */

    /* 当前DMA模式 */
    enum oled_dma_mode dma_mode;

    /* DMA完成等待 */
    struct completion dma_completion;

    /* DMA统计 */
    struct oled_dma_stats stats;

    /* 同步保护 */
    struct mutex buf_lock;          /* 缓冲区访问锁 */
    spinlock_t stats_lock;          /* 统计信息锁 */

    /* 工作队列 - 用于延迟刷新 */
    struct delayed_work refresh_work;
    bool refresh_pending;

    /* 设备状态 */
    bool powered_on;
    u8 contrast;
};

/* 函数声明 */

/* 驱动核心函数 */
int oled_hw_init(struct spi_oled_device *oled);
void oled_hw_reset(struct spi_oled_device *oled);
int oled_write_cmd(struct spi_oled_device *oled, u8 cmd);
int oled_write_cmd_seq(struct spi_oled_device *oled, const u8 *cmds, size_t len);

/* DMA相关函数 */
int oled_dma_init(struct spi_oled_device *oled);
void oled_dma_cleanup(struct spi_oled_device *oled);
int oled_dma_transfer_coherent(struct spi_oled_device *oled);
int oled_dma_transfer_streaming(struct spi_oled_device *oled);
int oled_dma_transfer_sg(struct spi_oled_device *oled);
int oled_pio_transfer(struct spi_oled_device *oled);

/* 显示刷新 */
int oled_refresh_display(struct spi_oled_device *oled);
void oled_convert_fb_to_oled(struct spi_oled_device *oled, u8 *dst);

/* Framebuffer接口 */
int oled_fb_register(struct spi_oled_device *oled);
void oled_fb_unregister(struct spi_oled_device *oled);

#endif /* _SPI_OLED_DMA_H_ */
posted @ 2026-02-26 16:04  21code  阅读(1)  评论(0)    收藏  举报