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 |