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 队列所使用的内存资源。
函数执行以下操作:
- 使用
dma_free_coherent函数释放 NVMe 队列的 CQ(完成队列)内存区域,即完成队列条目数组。释放的内存大小为CQ_SIZE(nvmeq)。 - 检查是否分配了 SQ(提交队列)命令内存区域,如果没有,则函数直接返回。
- 如果 SQ 使用了 Combined Memory Buffer(CMB),则通过
pci_free_p2pmem函数释放 SQ 内存区域,该函数用于释放 P2P 内存。释放的内存大小为SQ_SIZE(nvmeq)。 - 如果 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是每个队列项的大小。
函数的实现逻辑如下:
- 计算每个队列需要的空间,将队列深度乘以每个队列项的大小,并将结果向上对齐到 NVME_CTRL_PAGE_SIZE 的倍数。
- 检查所有队列在 CMB 中占用的总空间是否超过了设备的 CMB 大小。
- 如果超过了 CMB 大小,则计算每个队列在 CMB 中分配的空间,并确保其大小为 NVME_CTRL_PAGE_SIZE 的倍数。
- 根据分配的空间计算每个队列的深度,并确保深度不小于 64。
- 返回计算得到的队列深度。
如果在计算过程中出现了问题(例如 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。
函数的实现逻辑如下:
- 如果队列不是 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 内存。
- 如果上述条件不满足,或者 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是队列的深度。
函数的实现逻辑如下:
- 如果
dev->ctrl.queue_count大于等于qid,则说明队列已经被分配过,直接返回 0 表示分配成功。 - 设置
nvmeq->sqes为qid不为 0 时的 I/O SQES,否则为 admin SQES。 - 设置
nvmeq->q_depth为传入的深度。 - 使用
dma_alloc_coherent分配一块连续的内存,大小为 CQ_SIZE(nvmeq),并将虚拟地址保存到nvmeq->cqes,将总线地址保存到nvmeq->cq_dma_addr。 - 如果 CQ 分配失败,跳转到
free_nvmeq标签,释放之前分配的内存,并返回 -ENOMEM,表示内存不足的错误。 - 调用
nvme_alloc_sq_cmds分配 SQ-CMB,如果分配失败,跳转到free_cqdma标签,释放之前分配的 CQ 内存,并返回 -ENOMEM,表示内存不足的错误。 - 设置其他队列属性:
-
nvmeq->dev指向设备。 - 初始化
nvmeq->sq_lock和nvmeq->cq_poll_lock自旋锁。 - 初始化
nvmeq->cq_head和nvmeq->cq_phase。 - 计算
nvmeq->q_db,指向队列所在的 Doorbell 寄存器地址。 - 设置
nvmeq->qid为传入的队列 ID。 - 增加
dev->ctrl.queue_count,表示已经分配的队列数。
- 返回 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 队列。
函数的实现逻辑如下:
- 从
nvmeq->dev获取设备的 PCI 设备信息,即pdev。 - 从
nvmeq->dev->ctrl.instance获取控制器的实例号,保存在nr变量中。 - 如果使用线程化中断,调用
pci_request_irq请求分配一个中断,传入以下参数:
-
pdev:PCI 设备指针。 -
nvmeq->cq_vector:中断向量号。 -
nvme_irq_check:中断处理函数,用于检查是否有待处理的中断。 -
nvme_irq:实际的中断处理函数。 -
nvmeq:传递给中断处理函数的参数。 -
"nvme%dq%d":中断名字的格式字符串,包含控制器的实例号和队列号。
- 如果不使用线程化中断,调用
pci_request_irq请求分配一个中断,传入以下参数:
-
pdev:PCI 设备指针。 -
nvmeq->cq_vector:中断向量号。 -
nvme_irq:实际的中断处理函数。 -
NULL:中断处理函数的线程上下文参数。 -
nvmeq:传递给中断处理函数的参数。 -
"nvme%dq%d":中断名字的格式字符串,包含控制器的实例号和队列号。
- 返回请求中断的结果。
这个函数的作用是根据是否启用线程化中断,为一个 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是队列的编号。
函数的实现逻辑如下:
- 从
nvmeq->dev获取 NVMe 设备的指针,保存在dev变量中。 - 初始化
nvmeq的一些队列相关的成员变量:
-
sq_tail:队列的发送指针,初始化为 0。 -
last_sq_tail:上一次队列的发送指针,初始化为 0。 -
cq_head:队列的完成指针,初始化为 0。 -
cq_phase:完成队列的相位,初始化为 1。 -
q_db:指向设备的 Doorbell 寄存器地址。
- 使用
memset将nvmeq的完成队列中的内容清零。 - 调用
nvme_dbbuf_init函数来初始化设备的 Doorbell 缓冲区。 - 增加设备的在线队列计数。
- 使用
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 设备。
函数的实现逻辑如下:
- 使用
mutex_trylock函数尝试获取shutdown_lock锁,如果锁已经被占用,返回错误码-ENODEV,表示设备不存在。 - 如果获取到锁,检查控制器的状态是否为
NVME_CTRL_CONNECTING,如果不是,释放锁并返回错误码-ENODEV,表示设备不存在。 - 如果控制器状态正确,返回 0,表示成功获取锁并且设备状态正常。
这个函数的目的是在设置 IO 队列之前,通过获取 shutdown_lock 锁来确保没有其他关键的操作正在进行,同时还检查控制器的状态以确保只在正确的状态下进行队列设置。
















