nvme prp模型代码处理流程分析

以下函数是prp相关的源码。

/*
 * prp模型,除了第一个dma addr不是page_size对齐的
	其余的dma addr都要求是page_size对齐的
*/
static blk_status_t nvme_pci_setup_prps(struct nvme_dev *dev, struct request *req, struct nvme_rw_command *cmnd)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	struct dma_pool *pool;
	__le64 *prp_list;
	dma_addr_t prp_dma;
	int nprps, i;
	int length = blk_rq_payload_bytes(req);//request的数据总长度, 假设 5000
	struct scatterlist *sg = iod->sg; //数据都映射在这里了
	int dma_len = sg_dma_len(sg); //dma mapping以后的数据长度 假设 4096
	u64 dma_addr = sg_dma_address(sg); //假设4096对齐或者不对齐,也就是有offset的情况
	u32 page_size = dev->ctrl.page_size; //这个假设是4096
	int offset = dma_addr & (page_size - 1);//求offset的方法,比较巧妙,根据这个offset可以知道一个sge要传输的数据长度(在确定page大小的情况下)
	void **list = nvme_pci_iod_list(req);

	length -= (page_size - offset);//判断1个prp是否满足要求 length = length - (page_size - offset) = 5000 - 4096
	if (length <= 0) {
		iod->first_dma = 0;
		goto done;
	}

	dma_len -= (page_size - offset); //dma_len = dma_len - (page_size - offset) = 4096 - 4096 = 0
	if (dma_len) {
		dma_addr += (page_size - offset); //dma_addr = dma_addr + (page_size - offset)
	} else { 
		sg = sg_next(sg); //如果走这个分支说明 dma_len == (page_size - offset)
		dma_addr = sg_dma_address(sg);
		dma_len = sg_dma_len(sg);
	}

	//这个成立说明只需要两个prp就可以了 不需要list
	if (length <= page_size) {
		iod->first_dma = dma_addr;
		goto done;
	}

	nprps = DIV_ROUND_UP(length, page_size); // nprps = int(length / page_size) + 1
	if (nprps <= (256 / 8)) { //nprps < 32
		pool = dev->prp_small_pool; //256 / 8 = 32
		iod->npages = 0;
	} else {
		pool = dev->prp_page_pool; //4096 / 8 = 512
		iod->npages = 1; //标记是使用哪一个pool
	}

	prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);
	if (!prp_list) {
		iod->first_dma = dma_addr;
		iod->npages = -1;
		return BLK_STS_RESOURCE;
	}
	list[0] = prp_list; //这个类似 nvme_pci_iod_list(req)[0] = prp_list 记录的是虚拟地址;
	iod->first_dma = prp_dma; //记录物理地址
	i = 0;
	for (;;) {
		if (i == page_size >> 3) { //4096 >> 3 == 512, 只有pool是prp_page_pool才会进入这个分支,当然还要保证iod->sg映射的物理段足够多
			__le64 *old_prp_list = prp_list;
			prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);//之前的记录满了,所以再次申请
			if (!prp_list)
				return BLK_STS_RESOURCE;
			list[iod->npages++] = prp_list;
			prp_list[0] = old_prp_list[i - 1];
			old_prp_list[i - 1] = cpu_to_le64(prp_dma);
			i = 1;
		}
		prp_list[i++] = cpu_to_le64(dma_addr); //prp_list里面记录的都是地址,记录一个需要8个字节
		dma_len -= page_size;
		dma_addr += page_size;
		length -= page_size;

		//这个成立说明数据已经全部转换完毕了
		if (length <= 0)
			break;
		if (dma_len > 0)
			continue;
		/*说明了sg_dma_address(sg)转换出来的地址不一定就是page_size对齐的,
			但是dma_len一定要是page_size的倍数。也就是offset为0
		*/
		if (unlikely(dma_len < 0)) 
			goto bad_sgl;
		sg = sg_next(sg);
		dma_addr = sg_dma_address(sg);
		dma_len = sg_dma_len(sg);
	}
done:
	cmnd->dptr.prp1 = cpu_to_le64(sg_dma_address(iod->sg)); //主要就是填这两个prp
	cmnd->dptr.prp2 = cpu_to_le64(iod->first_dma);//第二个prp记录数据或者下一个dma pool的起始地址
	return BLK_STS_OK;
bad_sgl:
	WARN(DO_ONCE(nvme_print_sgl, iod->sg, iod->nents), "Invalid SGL for payload:%d nents:%d\n", blk_rq_payload_bytes(req), iod->nents);
	return BLK_STS_IOERR;
}

一般来说,如果sge只有1个时,那么只需要在下发的nvme rw命令时填写prp1字段的值,这个值是64bit的,所以这个64bit的字段即需要有dma的地址,也需要有传输的长度,这个64bit所以它需要一些特定的bit记录地址,一些特定的bit记录长度,同理prp2也是。
但是如果sge段比较多,靠prp1和prp2就不够了,所以需要更多的prp,这个时候prp2里记录的地址就是prp_list的起始地址了,但是,prp_list有个限制条件,就是它的每个64位的bit记录的都是地址,而没有长度,长度都是nvme 的page_size大小。

在这里插入图片描述
从上面的分析当中,有两个疑问?
1:怎么知道prp2记录的是数据的地址还是prp_list的起始地址?
2:需要多个prp时,因为prp_list是只记录地址的,但是有时候一个IO请求时,数据量没那么巧怎么办?也就是prp_list最后元素记录的地址传输的数据量不是一个page_size?

关于第二点,在nvme_map_data函数里有这么一段代码,metadata有特殊用途?

//这个if语句是为metadata做map操作,然后把dma地址给到cmnd->rw.metadata成员 看起来数据量应该不是太大
	if (blk_integrity_rq(req)) { 
		if (blk_rq_count_integrity_sg(q, req->bio) != 1)
			goto out_unmap;

		sg_init_table(&iod->meta_sg, 1);
		if (blk_rq_map_integrity_sg(q, req->bio, &iod->meta_sg) != 1)
			goto out_unmap;

		if (!dma_map_sg(dev->dev, &iod->meta_sg, 1, dma_dir))
			goto out_unmap;
	}

	if (blk_integrity_rq(req))
		cmnd->rw.metadata = cpu_to_le64(sg_dma_address(&iod->meta_sg));
	return BLK_STS_OK;

第一点,如果有知道的同仁可以留给言。

最后看一下,关于选择sgl还是prp的依据。

static inline bool nvme_pci_use_sgls(struct nvme_dev *dev, struct request *req)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	int nseg = blk_rq_nr_phys_segments(req);
	unsigned int avg_seg_size;

	if (nseg == 0)
		return false;

	avg_seg_size = DIV_ROUND_UP(blk_rq_payload_bytes(req), nseg); //int(blk_rq_payload_bytes(req)/nseg) + 1
	if (!(dev->ctrl.sgls & ((1 << 0) | (1 << 1)))) //判断sgls的第0第1个bit是否为1
		return false;
	if (!iod->nvmeq->qid) //这个是管理队列
		return false;
	if (!sgl_threshold || avg_seg_size < sgl_threshold)//数据量比较小时使用prp
		return false;
	return true;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值