nvme_free_queue

static void nvme_free_queue(struct nvme_queue *nvmeq)
{
	dma_free_coherent(nvmeq->dev->dev, CQ_SIZE(nvmeq),
				(void *)nvmeq->cqes, nvmeq->cq_dma_addr);
	if (!nvmeq->sq_cmds)
		return;

	if (test_and_clear_bit(NVMEQ_SQ_CMB, &nvmeq->flags)) {
		pci_free_p2pmem(to_pci_dev(nvmeq->dev->dev),
				nvmeq->sq_cmds, SQ_SIZE(nvmeq));
	} else {
		dma_free_coherent(nvmeq->dev->dev, SQ_SIZE(nvmeq),
				nvmeq->sq_cmds, nvmeq->sq_dma_addr);
	}
}

这段代码定义了一个名为 nvme_free_queue 的函数,用于释放 NVMe 队列所使用的内存资源。

函数执行以下操作:

  1. 使用 dma_free_coherent 函数释放 NVMe 队列的 CQ(完成队列)内存区域,即完成队列条目数组。释放的内存大小为 CQ_SIZE(nvmeq)
  2. 检查是否分配了 SQ(提交队列)命令内存区域,如果没有,则函数直接返回。
  3. 如果 SQ 使用了 Combined Memory Buffer(CMB),则通过 pci_free_p2pmem 函数释放 SQ 内存区域,该函数用于释放 P2P 内存。释放的内存大小为 SQ_SIZE(nvmeq)
  4. 如果 SQ 没有使用 CMB,同样使用 dma_free_coherent 函数释放 SQ 内存区域。释放的内存大小为 SQ_SIZE(nvmeq)

综上所述,nvme_free_queue 函数用于释放 NVMe 队列所使用的内存资源,包括完成队列和提交队列的内存区域。如果提交队列使用了 CMB,则会使用专门的函数进行释放。

nvme_free_queues

static void nvme_free_queues(struct nvme_dev *dev, int lowest)
{
	int i;

	for (i = dev->ctrl.queue_count - 1; i >= lowest; i--) {
		dev->ctrl.queue_count--;
		nvme_free_queue(&dev->queues[i]);
	}
}

这段代码定义了一个函数 nvme_free_queues,用于释放 NVMe 设备的队列及其相关的内存资源。

函数接受两个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示要释放队列的 NVMe 设备。
  • lowest 是一个整数,表示要释放的队列的最低索引。

函数通过一个循环从最高队列索引开始逐个释放队列,直到达到 lowest 索引为止。在每个迭代中,它首先将设备的 queue_count 减少 1,然后调用函数 nvme_free_queue 来释放相应队列的内存资源。

总之,nvme_free_queues 函数的目的是批量释放 NVMe 设备的队列及其相关内存资源,以便在设备不再需要这些资源时进行清理。

nvme_suspend_queue

static void nvme_suspend_queue(struct nvme_dev *dev, unsigned int qid)
{
	struct nvme_queue *nvmeq = &dev->queues[qid];

	if (!test_and_clear_bit(NVMEQ_ENABLED, &nvmeq->flags))
		return;

	/* ensure that nvme_queue_rq() sees NVMEQ_ENABLED cleared */
	mb();

	nvmeq->dev->online_queues--;
	if (!nvmeq->qid && nvmeq->dev->ctrl.admin_q)
		nvme_quiesce_admin_queue(&nvmeq->dev->ctrl);
	if (!test_and_clear_bit(NVMEQ_POLLED, &nvmeq->flags))
		pci_free_irq(to_pci_dev(dev->dev), nvmeq->cq_vector, nvmeq);
}

这段代码定义了一个函数 nvme_suspend_queue,用于挂起(暂停)指定队列的操作。

函数接受两个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。
  • qid 是一个无符号整数,表示要挂起的队列的索引。

函数首先检查队列的标志位 NVMEQ_ENABLED 是否已设置,如果没有设置,则不执行任何操作。接着,它清除标志位 NVMEQ_ENABLED,表示该队列已被挂起。

然后,函数会减少设备的 online_queues 计数器。如果挂起的是管理员队列(qid 为0),且管理员队列存在,则会调用函数 nvme_quiesce_admin_queue 来挂起管理员队列的操作。

最后,函数会根据队列的标志位 NVMEQ_POLLED 来决定是否释放队列的中断资源。如果 NVMEQ_POLLED 没有设置,表示队列使用中断,那么会使用 pci_free_irq 函数释放队列的中断。

总之,nvme_suspend_queue 函数用于暂停指定的 NVMe 设备队列,包括清除队列的状态标志、挂起队列操作以及释放相关的中断资源。

nvme_suspend_io_queues

static void nvme_suspend_io_queues(struct nvme_dev *dev)
{
	int i;

	for (i = dev->ctrl.queue_count - 1; i > 0; i--)
		nvme_suspend_queue(dev, i);
}

这段代码定义了一个函数 nvme_suspend_io_queues,用于挂起所有的输入/输出(I/O)队列,除了管理员队列。

函数接受一个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。

函数通过循环遍历设备中的所有队列(除了管理员队列,即索引0),调用函数 nvme_suspend_queue 对每个队列进行挂起操作。这样,函数会将所有的 I/O 队列都挂起,以暂停它们的操作。

nvme_reap_pending_cqes

/*
 * Called only on a device that has been disabled and after all other threads
 * that can check this device's completion queues have synced, except
 * nvme_poll(). This is the last chance for the driver to see a natural
 * completion before nvme_cancel_request() terminates all incomplete requests.
 */
static void nvme_reap_pending_cqes(struct nvme_dev *dev)
{
	int i;

	for (i = dev->ctrl.queue_count - 1; i > 0; i--) {
		spin_lock(&dev->queues[i].cq_poll_lock);
		nvme_poll_cq(&dev->queues[i], NULL);
		spin_unlock(&dev->queues[i].cq_poll_lock);
	}
}

这段代码定义了一个函数 nvme_reap_pending_cqes,用于在设备被禁用后、所有能够检查该设备完成队列的其他线程都同步后调用。这个函数的作用是在 nvme_cancel_request() 终止所有未完成的请求之前,为驱动程序提供一个最后的机会来查看自然完成的请求。

函数接受一个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。

函数的实现通过循环遍历设备中的所有 I/O 队列(除了管理员队列,即索引0),并对每个队列的完成队列进行轮询以收集未完成的请求。这样,函数在设备被禁用之前最后一次检查未完成的请求的状态,以便做出适当的处理。

nvme_cmb_qdepth

static int nvme_cmb_qdepth(struct nvme_dev *dev, int nr_io_queues,
				int entry_size)
{
	int q_depth = dev->q_depth;
	unsigned q_size_aligned = roundup(q_depth * entry_size,
					  NVME_CTRL_PAGE_SIZE);

	if (q_size_aligned * nr_io_queues > dev->cmb_size) {
		u64 mem_per_q = div_u64(dev->cmb_size, nr_io_queues);

		mem_per_q = round_down(mem_per_q, NVME_CTRL_PAGE_SIZE);
		q_depth = div_u64(mem_per_q, entry_size);

		/*
		 * Ensure the reduced q_depth is above some threshold where it
		 * would be better to map queues in system memory with the
		 * original depth
		 */
		if (q_depth < 64)
			return -ENOMEM;
	}

	return q_depth;
}

这段代码定义了一个函数 nvme_cmb_qdepth,用于计算每个 I/O 队列在 Combined Memory Buffer (CMB) 中的队列深度。

函数接受三个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。
  • nr_io_queues 是 I/O 队列的数量。
  • entry_size 是每个队列项的大小。

函数的实现逻辑如下:

  1. 计算每个队列需要的空间,将队列深度乘以每个队列项的大小,并将结果向上对齐到 NVME_CTRL_PAGE_SIZE 的倍数。
  2. 检查所有队列在 CMB 中占用的总空间是否超过了设备的 CMB 大小。
  3. 如果超过了 CMB 大小,则计算每个队列在 CMB 中分配的空间,并确保其大小为 NVME_CTRL_PAGE_SIZE 的倍数。
  4. 根据分配的空间计算每个队列的深度,并确保深度不小于 64。
  5. 返回计算得到的队列深度。

如果在计算过程中出现了问题(例如 CMB 空间不足),则返回 -ENOMEM,表示内存不足的错误。否则,返回计算得到的队列深度。

nvme_alloc_sq_cmds

static int nvme_alloc_sq_cmds(struct nvme_dev *dev, struct nvme_queue *nvmeq,
				int qid)
{
	struct pci_dev *pdev = to_pci_dev(dev->dev);

	if (qid && dev->cmb_use_sqes && (dev->cmbsz & NVME_CMBSZ_SQS)) {
		nvmeq->sq_cmds = pci_alloc_p2pmem(pdev, SQ_SIZE(nvmeq));
		if (nvmeq->sq_cmds) {
			nvmeq->sq_dma_addr = pci_p2pmem_virt_to_bus(pdev,
							nvmeq->sq_cmds);
			if (nvmeq->sq_dma_addr) {
				set_bit(NVMEQ_SQ_CMB, &nvmeq->flags);
				return 0;
			}

			pci_free_p2pmem(pdev, nvmeq->sq_cmds, SQ_SIZE(nvmeq));
		}
	}

	nvmeq->sq_cmds = dma_alloc_coherent(dev->dev, SQ_SIZE(nvmeq),
				&nvmeq->sq_dma_addr, GFP_KERNEL);
	if (!nvmeq->sq_cmds)
		return -ENOMEM;
	return 0;
}

这段代码定义了一个函数 nvme_alloc_sq_cmds,用于分配 I/O 队列的命令环(Submission Queue Command Entry Memory Buffer,SQ-CMB)。

函数接受三个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。
  • nvmeq 是一个指向 struct nvme_queue 的指针,表示要分配 SQ-CMB 的队列。
  • qid 是队列的 ID。

函数的实现逻辑如下:

  1. 如果队列不是 admin 队列,并且设备支持使用 SQ-CMB,并且设备的 CMB 大小中包含 SQS(Submission Queue Size)位:
  • 使用 pci_alloc_p2pmem 分配 P2P 内存,大小为 SQ_SIZE(nvmeq)。P2P 内存是一种可被 PCI 设备和 CPU 访问的内存。
  • 如果分配成功,将分配的 P2P 内存的虚拟地址转换为总线地址,并设置 SQ-CMB 标志。
  • 如果总线地址转换成功,返回 0,表示成功分配 SQ-CMB。
  • 如果总线地址转换失败,释放分配的 P2P 内存。
  1. 如果上述条件不满足,或者 P2P 内存分配失败:
  • 使用 dma_alloc_coherent 分配一块连续的内存,大小为 SQ_SIZE(nvmeq)。
  • 如果分配成功,将返回的内存的虚拟地址保存到 nvmeq->sq_cmds,并将总线地址保存到 nvmeq->sq_dma_addr
  • 如果分配失败,返回 -ENOMEM,表示内存不足的错误。

最终,函数返回 0 表示成功分配 SQ-CMB,或返回 -ENOMEM 表示内存不足。

nvme_alloc_queue

static int nvme_alloc_queue(struct nvme_dev *dev, int qid, int depth)
{
	struct nvme_queue *nvmeq = &dev->queues[qid];

	if (dev->ctrl.queue_count > qid)
		return 0;

	nvmeq->sqes = qid ? dev->io_sqes : NVME_ADM_SQES;
	nvmeq->q_depth = depth;
	nvmeq->cqes = dma_alloc_coherent(dev->dev, CQ_SIZE(nvmeq),
					 &nvmeq->cq_dma_addr, GFP_KERNEL);
	if (!nvmeq->cqes)
		goto free_nvmeq;

	if (nvme_alloc_sq_cmds(dev, nvmeq, qid))
		goto free_cqdma;

	nvmeq->dev = dev;
	spin_lock_init(&nvmeq->sq_lock);
	spin_lock_init(&nvmeq->cq_poll_lock);
	nvmeq->cq_head = 0;
	nvmeq->cq_phase = 1;
	nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];
	nvmeq->qid = qid;
	dev->ctrl.queue_count++;

	return 0;

 free_cqdma:
	dma_free_coherent(dev->dev, CQ_SIZE(nvmeq), (void *)nvmeq->cqes,
			  nvmeq->cq_dma_addr);
 free_nvmeq:
	return -ENOMEM;
}

这段代码定义了一个函数 nvme_alloc_queue,用于分配 NVMe 队列。

函数接受三个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。
  • qid 是队列的 ID。
  • depth 是队列的深度。

函数的实现逻辑如下:

  1. 如果 dev->ctrl.queue_count 大于等于 qid,则说明队列已经被分配过,直接返回 0 表示分配成功。
  2. 设置 nvmeq->sqesqid 不为 0 时的 I/O SQES,否则为 admin SQES。
  3. 设置 nvmeq->q_depth 为传入的深度。
  4. 使用 dma_alloc_coherent 分配一块连续的内存,大小为 CQ_SIZE(nvmeq),并将虚拟地址保存到 nvmeq->cqes,将总线地址保存到 nvmeq->cq_dma_addr
  5. 如果 CQ 分配失败,跳转到 free_nvmeq 标签,释放之前分配的内存,并返回 -ENOMEM,表示内存不足的错误。
  6. 调用 nvme_alloc_sq_cmds 分配 SQ-CMB,如果分配失败,跳转到 free_cqdma 标签,释放之前分配的 CQ 内存,并返回 -ENOMEM,表示内存不足的错误。
  7. 设置其他队列属性:
  • nvmeq->dev 指向设备。
  • 初始化 nvmeq->sq_locknvmeq->cq_poll_lock 自旋锁。
  • 初始化 nvmeq->cq_headnvmeq->cq_phase
  • 计算 nvmeq->q_db,指向队列所在的 Doorbell 寄存器地址。
  • 设置 nvmeq->qid 为传入的队列 ID。
  • 增加 dev->ctrl.queue_count,表示已经分配的队列数。
  1. 返回 0,表示成功分配队列。

如果在分配队列过程中遇到内存不足的情况,会释放之前分配的内存,并返回 -ENOMEM。

queue_request_irq

static int queue_request_irq(struct nvme_queue *nvmeq)
{
	struct pci_dev *pdev = to_pci_dev(nvmeq->dev->dev);
	int nr = nvmeq->dev->ctrl.instance;

	if (use_threaded_interrupts) {
		return pci_request_irq(pdev, nvmeq->cq_vector, nvme_irq_check,
				nvme_irq, nvmeq, "nvme%dq%d", nr, nvmeq->qid);
	} else {
		return pci_request_irq(pdev, nvmeq->cq_vector, nvme_irq,
				NULL, nvmeq, "nvme%dq%d", nr, nvmeq->qid);
	}
}

这段代码定义了一个函数 queue_request_irq,用于请求分配一个队列的中断。根据是否使用线程化中断来决定具体的中断处理函数。

函数接受一个参数:

  • nvmeq 是一个指向 struct nvme_queue 的指针,表示一个 NVMe 队列。

函数的实现逻辑如下:

  1. nvmeq->dev 获取设备的 PCI 设备信息,即 pdev
  2. nvmeq->dev->ctrl.instance 获取控制器的实例号,保存在 nr 变量中。
  3. 如果使用线程化中断,调用 pci_request_irq 请求分配一个中断,传入以下参数:
  • pdev:PCI 设备指针。
  • nvmeq->cq_vector:中断向量号。
  • nvme_irq_check:中断处理函数,用于检查是否有待处理的中断。
  • nvme_irq:实际的中断处理函数。
  • nvmeq:传递给中断处理函数的参数。
  • "nvme%dq%d":中断名字的格式字符串,包含控制器的实例号和队列号。
  1. 如果不使用线程化中断,调用 pci_request_irq 请求分配一个中断,传入以下参数:
  • pdev:PCI 设备指针。
  • nvmeq->cq_vector:中断向量号。
  • nvme_irq:实际的中断处理函数。
  • NULL:中断处理函数的线程上下文参数。
  • nvmeq:传递给中断处理函数的参数。
  • "nvme%dq%d":中断名字的格式字符串,包含控制器的实例号和队列号。
  1. 返回请求中断的结果。

这个函数的作用是根据是否启用线程化中断,为一个 NVMe 队列请求分配一个中断,并将相应的中断处理函数与中断向量关联起来。

nvme_init_queue

static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid)
{
	struct nvme_dev *dev = nvmeq->dev;

	nvmeq->sq_tail = 0;
	nvmeq->last_sq_tail = 0;
	nvmeq->cq_head = 0;
	nvmeq->cq_phase = 1;
	nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];
	memset((void *)nvmeq->cqes, 0, CQ_SIZE(nvmeq));
	nvme_dbbuf_init(dev, nvmeq, qid);
	dev->online_queues++;
	wmb(); /* ensure the first interrupt sees the initialization */
}

这段代码定义了一个函数 nvme_init_queue,用于初始化一个 NVMe 队列。

函数接受两个参数:

  • nvmeq 是一个指向 struct nvme_queue 的指针,表示要初始化的 NVMe 队列。
  • qid 是队列的编号。

函数的实现逻辑如下:

  1. nvmeq->dev 获取 NVMe 设备的指针,保存在 dev 变量中。
  2. 初始化 nvmeq 的一些队列相关的成员变量:
  • sq_tail:队列的发送指针,初始化为 0。
  • last_sq_tail:上一次队列的发送指针,初始化为 0。
  • cq_head:队列的完成指针,初始化为 0。
  • cq_phase:完成队列的相位,初始化为 1。
  • q_db:指向设备的 Doorbell 寄存器地址。
  1. 使用 memsetnvmeq 的完成队列中的内容清零。
  2. 调用 nvme_dbbuf_init 函数来初始化设备的 Doorbell 缓冲区。
  3. 增加设备的在线队列计数。
  4. 使用 wmb() 增加内存屏障,确保队列的初始化在第一个中断前完成。

这个函数的作用是初始化一个 NVMe 队列,将相关的队列成员变量初始化,并确保初始化在中断处理过程中的可见性。

nvme_setup_io_queues_trylock

/*
 * Try getting shutdown_lock while setting up IO queues.
 */
static int nvme_setup_io_queues_trylock(struct nvme_dev *dev)
{
	/*
	 * Give up if the lock is being held by nvme_dev_disable.
	 */
	if (!mutex_trylock(&dev->shutdown_lock))
		return -ENODEV;

	/*
	 * Controller is in wrong state, fail early.
	 */
	if (dev->ctrl.state != NVME_CTRL_CONNECTING) {
		mutex_unlock(&dev->shutdown_lock);
		return -ENODEV;
	}

	return 0;
}

这段代码定义了一个函数 nvme_setup_io_queues_trylock,用于尝试在设置 IO 队列时获取 shutdown_lock 锁。

函数接受一个参数:

  • dev 是一个指向 struct nvme_dev 的指针,表示 NVMe 设备。

函数的实现逻辑如下:

  1. 使用 mutex_trylock 函数尝试获取 shutdown_lock 锁,如果锁已经被占用,返回错误码 -ENODEV,表示设备不存在。
  2. 如果获取到锁,检查控制器的状态是否为 NVME_CTRL_CONNECTING,如果不是,释放锁并返回错误码 -ENODEV,表示设备不存在。
  3. 如果控制器状态正确,返回 0,表示成功获取锁并且设备状态正常。

这个函数的目的是在设置 IO 队列之前,通过获取 shutdown_lock 锁来确保没有其他关键的操作正在进行,同时还检查控制器的状态以确保只在正确的状态下进行队列设置。