본문 바로가기

리눅스 커널/블록 멀티 큐

nvme command

i/o completion queue 만들기

Queue size : CQ내에 entry 개수. 2Bytes

Queue Identifier : CQ의 id. 2Bytes

Interrupt Vector : MSI-X 또는 MSI vector 번호. 2Bytes

IEN : Interrupt Enable, 1 == enable. 1bit

PC : Physically Contiguous, 1 == SQ는 물리적으로 호스트 메모리에서 연속적이다. 1bit

PRP Entry 1 : 물리적으로 연속적이지 않을 때, 호스트 페이지를 포함하는 PRP list에 포인터이다. 8Bytes

enum nvme_admin_opcode {
	nvme_admin_delete_sq		= 0x00,
	nvme_admin_create_sq		= 0x01,
	nvme_admin_delete_cq		= 0x04,
	nvme_admin_create_cq		= 0x05,
	...
};

enum {
	NVME_QUEUE_PHYS_CONTIG	= (1 << 0),
	NVME_CQ_IRQ_ENABLED	= (1 << 1),
	NVME_SQ_PRIO_URGENT	= (0 << 1),
	NVME_SQ_PRIO_HIGH	= (1 << 1),
	NVME_SQ_PRIO_MEDIUM	= (2 << 1),
	NVME_SQ_PRIO_LOW	= (3 << 1),
};
static int adapter_alloc_cq(struct nvme_dev *dev, u16 qid,
						struct nvme_queue *nvmeq)
{
	struct nvme_command c;
	int flags = NVME_QUEUE_PHYS_CONTIG | NVME_CQ_IRQ_ENABLED;

	memset(&c, 0, sizeof(c));
	c.create_cq.opcode = nvme_admin_create_cq;
	c.create_cq.prp1 = cpu_to_le64(nvmeq->cq_dma_addr);
	c.create_cq.cqid = cpu_to_le16(qid);
	c.create_cq.qsize = cpu_to_le16(nvmeq->q_depth - 1);
	c.create_cq.cq_flags = cpu_to_le16(flags);
	c.create_cq.irq_vector = cpu_to_le16(nvmeq->cq_vector);

	return nvme_submit_sync_cmd(dev->ctrl.admin_q, &c, NULL, 0);
}

flag는 물리적으로 연속적이며, IRQ가 가능하다는 것을 의미한다. -> 0x11, IEN == 1, PC == 1

cq_dma_addr 가 PRP Entry1으로 쓰인다.

 

i/o submission queue 만들기

Completion Queue Identifier : CQ 생성과 달리, 생성할 SQ와 관련된 CQ ID를 요구하고 있다. 이는 CQ를 먼저 만들어야만 SQ를 만들 수 있다는 것을 의미한다.

QPRIO : Queue의 우선순위, WRR에서 사용된다. 2bits

Queue Identifier : SQ의 id. 2Bytes

 

Command Specific Error Value

->CQ 가 유효하지 않음.

->Queue 식별자가 유효하지 않음.

->최대 큐 크기를 초과한 경우.

static int adapter_alloc_sq(struct nvme_dev *dev, u16 qid,
						struct nvme_queue *nvmeq)
{
	struct nvme_command c;
	int flags = NVME_QUEUE_PHYS_CONTIG;

	memset(&c, 0, sizeof(c));
	c.create_sq.opcode = nvme_admin_create_sq;
	c.create_sq.prp1 = cpu_to_le64(nvmeq->sq_dma_addr);
	c.create_sq.sqid = cpu_to_le16(qid);
	c.create_sq.qsize = cpu_to_le16(nvmeq->q_depth - 1);
	c.create_sq.sq_flags = cpu_to_le16(flags);
	c.create_sq.cqid = cpu_to_le16(qid);

	return nvme_submit_sync_cmd(dev->ctrl.admin_q, &c, NULL, 0);
}

flag는 물리적으로 연속적이다는 것을 의미한다. -> 0x01, PC == 1

cf, 여기에 NVME_SQ_PRIO_XXX 값을 OR 하여 WRR기능을 할 수 있을 것 같다.

 

sqid와 cqid를 모두 동일한 qid를 사용하는데 이는 nvme_queue  = {SQ, CQ}이고,

SQ와 CQ는 별개의 큐 이기 때문에, 겹쳐도 되는 것이다.

그래서 nvme_queue = {SQ, SQ, CQ} 또는 nvme_queue = {SQ, CQ, CQ} 인 경우에는

각 adapter_alloc_XX() 함수 호출 시, 인자로 별개의 값을 전달해야 될 것이다.

 

cpu_to_lexx() 는 인자로 전달받은 값의 endian을 변경해준다.

le가 little endian일 거 같다.

adpater_alloc_cq(), adpater_alloc_sq()  모두 nvme spec에 맞추어 구현되어 있다는 것을 쉽게 알 수 있다.

다만 qsize를 q_depth - 1을 하는 이유는 정확히 잘 모르겠다.

 

사진출처

https://www.flashmemorysummit.com/English/Collaterals/Proceedings/2013/20130812_PreConfD_Marks.pdf

 

'리눅스 커널 > 블록 멀티 큐' 카테고리의 다른 글

blk_mq_alloc_tag_set 흐름  (0) 2021.10.26
nvme command 관련 추가 내용  (0) 2021.10.26
nvme_setup_io_queues 흐름  (0) 2021.10.26
nvme_reset_work 흐름  (0) 2021.10.26
nvme 자료구조 관계  (0) 2021.10.26