PCIe-块设备驱动-SG DMA

本文深入探讨PCI Express(PCIe)块设备驱动的实现,重点关注使用 Scatter-Gather DMA(SG DMA)技术来优化数据传输。通过SG DMA,设备可以高效地处理分散在内存多个位置的数据,提升I/O性能。内容包括PCIe设备驱动的基本结构,SG列表的构建,以及如何在驱动中集成SG DMA机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >




/*
 * kernel: 4.5.2
 */

#include <linux/module.h>
#include <linux/pci.h>
#include <linux/blkdev.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>	// needed in 4.3.3

#define TEST_PCIE_DEV_NAME "test_pcie"
//#define PCI_VENDOR_ID_XILINX 0x10EE	/* already defined in <linux/pci_ids.h> The default value, 10EEh, is the Vendor ID for Xilinx. */
#define TEST_PCI_DEVICE_ID_XILINX	0x7033	// !!!check here!	/* the default value is 70<link speed><link width>h */

#define TEST_SSD_DEV_NAME		"test_ssd"
#define TEST_SSD_PARTITONS	1
#define TEST_SSD_MINORS 1
#define TEST_DEV_MEM_SZ	(1 << 22)	// 4MB

#define KTHREAD_NAME "test_kthread_fn"

#define COHERENT_DMA_BUF_SZ (1 << 22)	// !!!4MB; no less than 1MB
#define TEST_DMA_BUF_MAX_NUM 320	// !!!

/* ZC706 AXI Address */
#define DDR3_BASE_ADDR 			(0x0 + 0x400000)	// !!!!!!
#define AXI_BAR0 				0x80800000
#define AXI_BAR1 				0x80000000
#define TRANS_BRAM_BASE_ADDR	0x81000000	
#define AXI_BAR2 0x40000000	// wb

/* offset from TRANS_BRAM_BASE_ADDR */
#define AXI_PCIE_CTL_OFFSET 0x8000
#define AXI_CDMA_LITE_OFFSET 0xc000

/* AXI to PCIe Base Address Translation Configuration Registers
 * offset from AXI_PCIE_CTL_OFFSET
 */
#define AXIBAR2PCIEBAR_0U_OFFSET 0x208
#define AXIBAR2PCIEBAR_0L_OFFSET 0x20c
#define AXIBAR2PCIEBAR_1U_OFFSET 0x210
#define AXIBAR2PCIEBAR_1L_OFFSET 0x214
#define AXIBAR2PCIEBAR_2U_OFFSET 0x218
#define AXIBAR2PCIEBAR_2L_OFFSET 0x21c

/* trans_desc_offset */
/*
#define NXTDESC_PNTR_OFFSET 0x00
#define DESC_SA_OFFSET 0x08
#define DESC_DA_OFFSET 0x10
#define DESC_CTL_OFFSET 0x18
#define DESC_STAT_OFFSET 0x1c
*/
/* cdma_reg_offset
 * offset from AXI_CDMA_LITE_OFFSET
 */
#define	CDMACR_OFFSET 0x00
#define	CDMASR_OFFSET 0x04
#define	CURDESC_PNTR_OFFSET 0x08
#define	TAILDESC_PNTR_OFFSET 0x10
#define	CDMA_SA_OFFSET 0x18
#define	CDMA_DA_OFFSET 0x20
#define	CDMA_BTT_OFFSET 0x28

struct io_cmd {
	struct bio *bio;	// !!!
	struct scatterlist *scatlist;
	dma_addr_t dma_addr;	// used by DMA controller of the device
	void *kvaddr;	// kernel virtual address, used by kernel and driver, especially to deal with data from userspace(__bio_kmap_atomic)
	uint32_t len;
};

struct io_que {
	struct bio_list bio_lst;	// !!!composed of bio, a singly-linked list of bios
	struct task_struct *task_s;
	struct io_cmd *io_cmd;	// above
	struct ssd_dev *ssd_dev;	// !!!below
	spinlock_t lock;
	uint8_t volatile is_busy;    // origin: unsigned int, DMA busy flag
};

struct ssd_dev {
	struct pci_dev *pci_dev;
	struct gendisk *disk;	// linux/genhd.h
	void __iomem *pci_bar;	// !!!!!!above, __iomem is needed
	struct io_que *dev_que;	// !!!above
};

#define TRANS_DESC_ALIGN 0x40	// !!!!!!64

struct trans_desc {	// transfer descriptor, according to xapp1171
	uint32_t nxt_ptr;
	uint32_t reserved0;
	uint32_t src_addr;
	uint32_t reserved1;
	uint32_t dest_addr;
	uint32_t reserved2;
	uint32_t ctrl;
	uint32_t stat;
};

#define __MIN(a, b) ((a) < (b) ? (a) : (b))

/*
static void setup_cmd(struct io_cmd *io_cmd, struct bio *bio, struct io_que *dev_que)
{
	io_cmd->bio = bio;	// !!!!!!save it until test_bio_complete
}
*/
/*
static int setup_scatter_map(struct ssd_dev *ssd_dev, struct io_cmd *io_cmd, unsigned int const phys_segs)
{
	void *kvaddr;	// volatile struct scatter_region *
	dma_addr_t dma_addr;

	// !!!!!!return two params! set here!
	kvaddr = dma_alloc_coherent(&ssd_dev->pci_dev->dev, PAGE_SIZE, &dma_addr, GFP_ATOMIC | GFP_DMA);	
	if (kvaddr == NULL) {
		printk("err_dma_pool_alloc\n");
		return -ENOMEM;
	}

	io_cmd->kvaddr = kvaddr;
	io_cmd->dma_addr = dma_addr;
	io_cmd->len = phys_segs;

	return 0;
}
*/
/*
static int setup_scatter_list(struct io_que *dev_que, struct io_cmd *io_cmd, struct bio *bio)
{
	//struct ssd_dev *ssd_dev;
	struct bio_vec prev_bv, cur_bv;	// !!!
	struct bvec_iter bvec_iter;	// !!!
	struct scatterlist *cur_scatlist = NULL;
	unsigned int phys_segs, bytes_len = 0;
	unsigned char isnt_first_bio_vec = 0u;	// !!!
	int result = -ENOMEM;
	
	phys_segs = bio_phys_segments(dev_que->ssd_dev->disk->queue, bio);	// !!!
	io_cmd->scatlist = (struct scatterlist *)kmalloc(sizeof(struct scatterlist) * phys_segs, GFP_ATOMIC | GFP_DMA);	// !!!
	if (io_cmd->scatlist == NULL) {
		printk("err_alloc_scatterlist\n");
		goto err_alloc_scatterlist;
	}

	sg_init_table(io_cmd->scatlist, phys_segs);	// !!!lib/scatterlist.c

	phys_segs = 0;
	memset(&prev_bv, 0, sizeof(struct bio_vec));	// !!!!!!prev_bv need to be initialized
	bio_for_each_segment(cur_bv, bio, bvec_iter) {	// !!!
		if (isnt_first_bio_vec && BIOVEC_PHYS_MERGEABLE(&prev_bv, &cur_bv)) {	// !!!BIOVEC_PHYS_MERGEABLE is defined in bio.h
			cur_scatlist->length += cur_bv.bv_len;	// !!!
		} else {
			if (isnt_first_bio_vec)
				cur_scatlist++;
			else
				cur_scatlist = io_cmd->scatlist;
			sg_set_page(cur_scatlist, cur_bv.bv_page, cur_bv.bv_len, cur_bv.bv_offset);	// !!!in <linux/scatterlist.h>
			phys_segs++;
		}
		bytes_len += cur_bv.bv_len;	// !!!
		prev_bv = cur_bv;
		isnt_first_bio_vec = 1u;
	}

	sg_mark_end(cur_scatlist);	// !!!<linux/scatterlist.h>

	//ssd_dev = dev_que->ssd_dev;
	result = dma_map_sg(&dev_que->ssd_dev->pci_dev->dev, io_cmd->scatlist, phys_segs,
				bio_data_dir(io_cmd->bio) == READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);	// !!!???What's its use?
	if (result == 0) {
		printk("err_dma_map_sg\n");
		goto err_dma_map_sg;
	}

	result = setup_scatter_map(dev_que->ssd_dev, io_cmd, phys_segs);	// above
	if (result)
		goto err_setup_scatter_map;

	bio->bi_iter.bi_sector += (sector_t)(bytes_len >> 9);	// !!!it will not be set by the kernel?
	bio->bi_iter.bi_idx = bvec_iter.bi_idx;	// !!!

	return 0;

err_setup_scatter_map:
	dma_unmap_sg(&dev_que->ssd_dev->pci_dev->d
<think>好的,用户想了解PCIE中的Scatter-Gather DMA的实现和使用方式。首先,我需要回忆一下DMA的基本概念,DMA(直接内存访问)允许外设直接访问系统内存,无需CPU介入,提高数据传输效率。而Scatter-Gather DMA则是更高级的形式,能够处理分散在内存不同位置的数据块。 接下来,PCIE作为一种高速串行总线标准,支持SG DMA是必然的。需要确认PCIE协议中是否有相关机制,比如描述符列表或地址转换支持。可能涉及到IOMMU(输入输出内存管理单元),用于地址映射和访问控制,确保安全性和隔离性,这在引用[2]中提到过。 然后,实现SG DMA需要硬件和软件协同工作。硬件部分可能包括DMA控制器支持描述符链表,每个描述符包含数据块的地址和长度。软件部分需要驱动程序设置这些描述符,并触发DMA传输。同时,IOMMU的作用是转换设备地址到物理地址,并检查权限,防止非法访问。 在应用场景方面,SG DMA适合处理非连续数据,如网络数据包、磁盘I/O等,提升吞吐量。优化方面可能涉及描述符缓存、批量处理减少中断,或者结合硬件特性如描述符预取等。 需要确保回答结构清晰,分点说明实现机制、工作流程、应用场景和优化技巧。同时引用提供的文献,比如提到IOMMU的作用时引用[2],或者在性能优化时引用[1]中提到的数据复制策略可能带来的性能提升,尽管传统认为应最小化数据复制,但某些情况下可能有反直觉的优化方法。 最后,生成相关问题时要围绕SG DMA的实现细节、与其他技术结合、性能分析、应用实例等方面提问,帮助用户深入理解。</think>### Scatter-Gather DMA in PCIe 的实现与应用 #### 1. 基本概念 Scatter-Gather DMA(分散-聚集直接内存访问)是一种高效的数据传输技术,允许设备通过单次DMA操作访问多个非连续内存区域。在PCIe中,这种技术通过**描述符链表**(Descriptor List)实现,每个描述符包含数据块的起始地址和长度[^2]。 #### 2. 实现机制 - **硬件支持**: - PCIe设备需集成DMA控制器,支持解析描述符链表。 - IOMMU(输入输出内存管理单元)负责地址转换和访问权限控制,确保设备只能访问授权的内存区域[^2]。 - 例如,Intel VT-d技术通过IOMMU实现地址映射和安全隔离。 - **软件配置**: - 驱动程序需在内存中构建描述符链表,格式通常为: ```c struct descriptor { uint64_t src_addr; uint64_t dst_addr; uint32_t length; uint32_t flags; // 控制位(如中断使能、结束标志) }; ``` - 通过PCIe配置空间将链表首地址写入设备的DMA寄存器。 #### 3. 工作流程 1. **初始化**:驱动程序分配内存并填充描述符。 2. **触发传输**:写入DMA控制寄存器启动传输。 3. **数据传输**: - DMA控制器按描述符逐个搬运数据。 - 使用PCIe事务层协议(TLP)完成数据传输。 4. **完成中断**:传输结束后,设备发送MSI/MSI-X中断通知CPU。 #### 4. 性能优化技巧 - **描述符预取**:设备提前缓存后续描述符,减少延迟。 - **批量提交**:一次性提交多个描述符,减少寄存器访问次数。 - **IOMMU优化**:利用IOVA(I/O虚拟地址)减少地址转换开销。 - **反直觉优化**:如引用[1]指出,特定场景下适度增加数据拷贝反而能提升性能(例如利用CPU缓存局部性)。 #### 5. 典型应用场景 - **网络数据包处理**:处理分散存储的报文头和载荷。 - **存储系统**:NVMe SSD控制器搬运非连续用户数据缓冲区。 - **GPU纹理传输**:聚合多个纹理碎片的内存地址。 ```c // 示例:简化的SG DMA初始化代码 void setup_sg_dma(struct device *dev, struct descriptor *desc) { // 将描述符链表物理地址写入设备寄存器 uint64_t dma_addr = iommu_map(dev, virt_to_phys(desc)); writel(dev->reg_base + DMA_ADDR_LO, dma_addr & 0xFFFFFFFF); writel(dev->reg_base + DMA_ADDR_HI, dma_addr >> 32); // 启动DMA writel(dev->reg_base + DMA_CTRL, DMA_START | DMA_IRQ_EN); } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值