이번에는 nvme_queue 구조체 안에 여러개의 SQ를 만들 차례이다.
nvme_reset_work() -> nvme_setup_io_queues() -> nvme_create_io_queues()
먼저 모든 nvme_queue는 원래대로 SQ를 각각 하나 씩 생성하게 한다.
그리고 이것이 성공하면 다음과 같이 모든 코어에 추가로 SQ를 생성하게 한다.
static int nvme_create_io_queues(struct nvme_dev *dev)
{
...
if (!ret) //all nvme queue was successfully created
{
int result = 0, extra_sq_cnt = 0, qid = 1;
while (!(result = nvme_create_extra_queue(dev->queues[qid], max)))
{
extra_sq_cnt += 1;
if (++qid > max)
qid = 1; //reset
}
printk("extra, additional sq count: %d\n", extra_sq_cnt);
}
else
printk("extra, do not attempt to create extra sq, err: %d\n", ret);
....
}
각 코어는 아래 함수를 이용하여 추가 SQ를 만들게 된다.
static int nvme_create_extra_queue(struct nvme_queue * nvmeq, unsigned int nrq)
{
int ret = -1, depth;
struct extra_queue * sq;
struct nvme_dev * dev = nvmeq->dev;
if (nvmeq->extra_cnt >= nvmeq->dev->extra_nr) //out of bound
return -1;
nvmeq->extra_list[nvmeq->extra_cnt] =
kzalloc(sizeof(struct extra_queue), GFP_KERNEL); //alloc memory for extra sq
sq = nvmeq->extra_list[nvmeq->extra_cnt]; //register to extra list
if (!sq)
return -1;
sq->size = 0; //reset
depth = dev->q_depth; //reset
sq->sq_cmds_extra = dma_alloc_coherent(dev->dev, SQ_SIZE(depth),
&sq->sq_dma_addr_extra, GFP_KERNEL);
//alloc array nvme_command
if (!sq->sq_cmds_extra) //fail
goto extra_handle;
nvmeq->extra_cnt += 1; //inc, the number of extra sq
sq->sq_tail_extra = 0; //reset
sq->qid_extra = nvmeq->qid + nrq * (nvmeq->extra_cnt); //new qid
sq->q_db_extra = &dev->dbs[sq->qid_extra * 2 * dev->db_stride]; //register doorbell
ret = adapter_alloc_sq_extra(dev, sq->qid_extra, nvmeq); //send command to device
if (!ret) //success
return ret;
//fail
err_handle:
nvmeq->extra_cnt -= 1;
dma_free_coherent(dev->dev, SQ_SIZE(depth), (void *)sq->sq_cmds_extra,
sq->sq_dma_addr_extra);
extra_handle:
kfree(sq);
return ret;
}
여기서 중요한 점은 door bell 설정이다. nvme spec에서 register definition을 이어서 살펴보면,
위와 같으며, Offset 관점으로 보면 다음과 같은 그림으로 정리할 수 있다.
SQ의 ID에 대해서 그 ID에 2를 곱한 값이 해당 SQ의 door bell register의 offset으로 볼 수 있는 것이다.
그리고 구현한 커널은 다음과 같은 구조를 갖는다.
상단이 코어를 나타내고, 하단의 3개가 각 코어에 할당된 SQ와 그 ID이다.
SQ의 생성시 Weight를 주기 위해서는 명령어를 다음과 같이 큐의 우선순위를 설정해 보내야한다.
이를 추가 큐들은 각각 HIGH, URGENT의 우선순위를 갖기 때문에 아래와 같이 추가 큐 생성을 위한 함수를 정의 하였다.
static int adapter_alloc_sq_extra(struct nvme_dev *dev, u16 qid,
struct nvme_queue *nvmeq)
{
struct nvme_command c;
int flags = NVME_QUEUE_PHYS_CONTIG;
if (qid <= 12)
flags |= NVME_SQ_PRIO_HIGH;
else
flags |= NVME_SQ_PRIO_URGENT;
/*
* Note: we (ab)use the fact the the prp fields survive if no data
* is attached to the request.
*/
memset(&c, 0, sizeof(c));
c.create_sq.opcode = nvme_admin_create_sq;
c.create_sq.prp1 =
cpu_to_le64(nvmeq->extra_list[nvmeq->extra_cnt - 1]->sq_dma_addr_extra);
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(nvmeq->qid);
return nvme_submit_sync_cmd(dev->ctrl.admin_q, &c, NULL, 0);
}
그리고 원래 갖고 있는 SQ는 다음과 같이 LOW의 Weight를 주었다.
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;
//original queue
//if (qid == 1)
//flags |= NVME_SQ_PRIO_URGENT;
//else if (qid > 1)
flags |= NVME_SQ_PRIO_LOW;
...
}
'리눅스 커널 > 블록 멀티 큐' 카테고리의 다른 글
NVMe device driver code tuning & comments - 5 (0) | 2021.10.26 |
---|---|
NVMe device driver code tuning & comments - 4 (0) | 2021.10.26 |
NVMe device driver code tuning & comments - 2 (0) | 2021.10.26 |
NVMe device driver code tuning & comments - 1 (0) | 2021.10.26 |
urgent queue 를 위한 tag 예약 (0) | 2021.10.26 |