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_ */

浙公网安备 33010602011771号