본문 바로가기

리눅스 커널/블록 멀티 큐

NVMe device driver code tuning & comments - 2

이번에는 nvme device driver 가 초기화 될 때 커널코드를 수정한 것을 그 흐름에 따라 정리해보겠다.

먼저 nvme_probe()가 디바이스 드라이버 초기화를 위해 제일 먼저 호출 된다.

static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	int node, result = -ENOMEM;
	struct nvme_dev *dev;
	unsigned long quirks = id->driver_data;

	node = dev_to_node(&pdev->dev);
	if (node == NUMA_NO_NODE)
		set_dev_node(&pdev->dev, first_memory_node);

	dev = kzalloc_node(sizeof(*dev), GFP_KERNEL, node);
	if (!dev)
		return -ENOMEM;

	dev->extra_nr = 32;
    ...
}

일단 nvme_queue가 소유 할 수 있는 최대 SQ의 개수를 32개로 하였다.

이 이상의 값으로 초기화 하였을 때 부팅 시 커널 패닉이 발생하는 문제가 있었다.

패닉이 발생하는 정확한 이유는 잘 모르겠지만, 일단은 되는대로 하였다.

 

이후 nvme_reset_work()가 호출되고 이 함수는 nvme_pci_configure_admin_queue()를 호출 한다.

그리고 이 함수는 nvme_enable_ctrl()을 호출하는데, 여기서 WRR로 스케줄링 하도록 설정 할 수 있다.

int nvme_enable_ctrl(struct nvme_ctrl *ctrl, u64 cap)
{
	/*
	 * Default to a 4K page size, with the intention to update this
	 * path in the future to accomodate architectures with differing
	 * kernel and IO page sizes.
	 */
	unsigned dev_page_min = NVME_CAP_MPSMIN(cap) + 12, page_shift = 12;
	int ret;

	if (page_shift < dev_page_min) {
		dev_err(ctrl->device,
			"Minimum device page size %u too large for host (%u)\n",
			1 << dev_page_min, 1 << page_shift);
		return -ENODEV;
	}

	ctrl->page_size = 1 << page_shift;

	ctrl->ctrl_config = NVME_CC_CSS_NVM;
	ctrl->ctrl_config |= (page_shift - 12) << NVME_CC_MPS_SHIFT;
	ctrl->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;
	ctrl->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;
	ctrl->ctrl_config |= NVME_CC_ENABLE;

	ctrl->ctrl_config |= NVME_CC_ARB_WRRU; //Use WRR sched.

	ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
	if (ret)
		return ret;
	return nvme_wait_ready(ctrl, cap, true);
}
EXPORT_SYMBOL_GPL(nvme_enable_ctrl);

기존은 NVME_CC_ARB_RR 즉, Round Robin으로 스케줄링을 하도록 되어있지만,

NVME_CC_ARB_WRRU, Weighted Round Robin으로 스케줄링 하도록 컨트롤러를 설정하는 것이다.

그리고 이 값을 cc에 설정하는 것이다.

그리고 이는 nvme 스펙과 관련이 있다.

그리고 해당 매크로 값은 다음과 같이 정의되어 있다.

enum {
	NVME_CC_ENABLE		= 1 << 0,
	NVME_CC_CSS_NVM		= 0 << 4,
	NVME_CC_MPS_SHIFT	= 7,
	NVME_CC_ARB_RR		= 0 << 11,
	NVME_CC_ARB_WRRU	= 1 << 11,
    ...
}

 

하지만 WRR을 디바이스 자체가 지원해야 하는데, 이에 대한 정보는 아래의 비트 정보를 통해 확인 할 수 있다.

 

테스트한 nvme ssd는 아래와 같았다.

0x33fff = 001(1) 0011 1111 1111 1111

17번째 bit가 1이므로 WRR을 지원하고 있다.

 

추가로 cap은 다음과 같이 디바이스로 부터 읽어 온다.

static int nvme_pci_enable(struct nvme_dev *dev)
{
	...

	dev->ctrl.cap = lo_hi_readq(dev->bar + NVME_REG_CAP);

	dev->q_depth = min_t(int, NVME_CAP_MQES(dev->ctrl.cap) + 1,
				io_queue_depth);
	dev->db_stride = 1 << NVME_CAP_STRIDE(dev->ctrl.cap);
	dev->dbs = dev->bar + 4096;
    
    ...
}