NVME driver分析之nvme_dev_start函数分析

 nvme_dev_start函数分析


1:nvme_dev_map@nvme_dev_start函数分析


①pci_enable_device_mem(pdev)//为user初始化pci device的memory space
②dev->entry[0].vector = pdev->irq;//给中断向量赋值
③pci_set_master(pdev);//使能pdev的bus_mastering
pci_select_bars(pdev, IORESOURCE_MEM);// Make BAR mask from the type of resource
pci_request_selected_regions(pdev, bars, "nvme")//预留出nvme的PCI I/O和memory的资源
④dev->bar = ioremap(pci_resource_start(pdev, 0), 8192);//将bar 0和bar 1 remap下来,remap的大小是8192
⑤cap = readq(&dev->bar->cap);//获取BAR中的cap属性信息
⑥dev->q_depth = min_t(int, NVME_CAP_MQES(cap) + 1, NVME_Q_DEPTH);//获取queue 的depth,就是queue中有多少个entry。取cap中规定的值和1024之中的最小值
⑦dev->db_stride = 1 << NVME_CAP_STRIDE(cap);//获取doorbell的步长
⑧dev->dbs = ((void __iomem *)dev->bar) + 4096; //获取doorbell register的基地址,参见spec第三章的图表

2: nvme_configure_admin_queue@nvme_dev_start函数分析


① nvme_disable_ctrl(dev, cap);//先disable掉掉Controller 然后才能进行后面的操作,操作完毕后重新enable controller 关闭control就是把CC_EN位置0,并且等待RDY位归0
②nvme_alloc_queue(dev, 0, 64, 0);//开辟queue的空间,下面是对该函数的详细分析
        nvmeq = kzalloc(sizeof(*nvmeq) + extra, GFP_KERNEL);//这里的extra是64个cmdinfo所占的空间(为什么是64个呢),cmd info包括一个回调函数和该回调函数所使用的参数
        nvmeq->cqes = dma_alloc_coherent(dmadev, CQ_SIZE(depth),   &nvmeq->cq_dma_addr, GFP_KERNEL);//为completion queues开辟DMA空间,大小是64* struct  nvme_completion
        nvmeq->sq_cmds = dma_alloc_coherent(dmadev, SQ_SIZE(depth),    &nvmeq->sq_dma_addr, GFP_KERNEL);//为submission queues开辟DMA空间,大小是64*struct nvme_command
        zalloc_cpumask_var(&nvmeq->cpu_mask, GFP_KERNEL)//初始化cpu mask,cpu mask用于cpu绑定io队列
         nvmeq->cq_head = 0; //将cq_head值初始化为 0 ,以后每处理一个completation会对head值进行加一操作
         nvmeq->cq_phase = 1;//将P位初始化为1,P位的主要作用是表明这是不是一个新的completion
         init_waitqueue_head(&nvmeq->sq_full);  ;//等待队列,nvme_kthread需要等待的等待队列
         init_waitqueue_entry(&nvmeq->sq_cong_wait, nvme_thread);//等待队列的entry,创建queue时会关联到等待队列
bio_list_init(&nvmeq->sq_cong);//
 INIT_LIST_HEAD(&nvmeq->iod_bio);//初始化iod_bio循环链表,iod_bio是kthread需要处理的bio请求
 nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];//Adimin Queue的Doorbell register的位置
 nvmeq->q_depth = depth;
 nvmeq->cq_vector = vector;
 nvmeq->qid = qid;
 nvmeq->q_suspended = 1;//将Admin queue的 suspend标志职位1
 dev->queue_count++;//queue的计数+1
 rcu_assign_pointer(dev->queues[qid], nvmeq);//将攒好的nvmeq卦在dev->nvme_queues上
③aqa = nvmeq->q_depth - 1;
 aqa |= aqa << 16; //计算Admin queue Attribute,后16bit是Admin completion Queue size,前16bit是Admin submission queue size

 dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM; //配置 controller config的值
 dev->ctrl_config |= (PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
 dev->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;
 dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;

⑤writel(aqa, &dev->bar->aqa); //将aqa值写入bar中
 writeq(nvmeq->sq_dma_addr, &dev->bar->asq); //将sq的dma地址写入bar中
 writeq(nvmeq->cq_dma_addr, &dev->bar->acq);
 writel(dev->ctrl_config, &dev->bar->cc);//将cont_config写入bar中

⑥nvme_enable_ctrl(dev, cap);//重新enable controller,并等待RDY重新归1 ,对应①
⑦queue_request_irq(dev, nvmeq, nvmeq->irqname);//申请中断,中断name是snprintf(nvmeq->irqname, sizeof(nvmeq->irqname), "nvme%dq%d",    dev->instance, qid);

⑧nvme_init_queue(nvmeq, 0);//对queue进行初始化操作
        nvmeq->sq_tail = 0;//初始化submission queue的tail
        nvmeq->cq_head = 0;//初始化completion queue 的head
        nvmeq->cq_phase = 1;//初始化P位为1
        nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];//Admin queue doorbell register 的基地址 
        memset(nvmeq->cmdid_data, 0, extra);//cmdid_data是一个bitmap,用于分配cmdid的,分配cmdid时查找这个bitmap找到哪一位为0,则这个位值就作为cmdid
        memset((void *)nvmeq->cqes, 0, CQ_SIZE(nvmeq->q_depth));
        nvme_cancel_ios(nvmeq, false); //清理掉queue中原来的 commands ,这部分主要是针对reset操作的,reset操作完毕以后也会call nvme_init_queue. reset-->resume---nvme_dev_start
        nvmeq->q_suspended = 0;//清理掉suspend标准,表明这个queue已经init过了,可以被使用了!!
        dev->online_queues++;//online_queue表明已经被init过的queue的个数

3:创建nvme_thread @nvme_dev_start
if (list_empty(&dev_list) && IS_ERR_OR_NULL(nvme_thread)) { //如果dev_list中没有dev(还没有任何controller被加入list),并且 nvme_thread还没有创建
  start_thread = true; //可以创建一个nvme_thread
  nvme_thread = NULL;
 }
 list_add(&dev->node, &dev_list); //将dev串到dev_list,就是将这个controller挂到controller list中。后面的操作中会对controller list中的controller遍历处理
 spin_unlock(&dev_list_lock);

 if (start_thread) {
  nvme_thread = kthread_run(nvme_kthread, NULL, "nvme"); //创建并启动一个nvme_kthread,这个thread的主要作用就是处理completion queue中的内容
  wake_up(&nvme_kthread_wait);
 } else
  wait_event_killable(nvme_kthread_wait, nvme_thread); //如果一个 nvme_thread  正在被创建,那么这个controller就应该等待,知道这个 nvme_thread  被创建OK
}
以上代码通过 start_thread 的flag和 nvme_kthread_wait的等待队列保证了多个controller只创建一个nvme_kthread

4:nvme_setup_io_queues(dev);@nvme_dev_start


①nr_io_queues = num_possible_cpus();
 result = set_queue_count(dev, nr_io_queues); //
        nvme_set_features(dev, NVME_FEAT_NUM_QUEUES, q_count, 0,      &result);//通过set feature 告诉controller有多少个io queue,queue的个数由cpu的个数决定
②if (size > 8192) 这里的判断的作用是,如果io queue的个数太多,导致doorbell register的空间超过了BAR0+BAR1的大小,那么需要重新对bar的size进行ioremap,这时候可能就用到了bar2 bar3等其他bar
③free_irq(dev->entry[0].vector, adminq);//在进行后面的操作之前,需要先关闭掉adminq的中断
④for (i = 0; i < nr_io_queues; i++) //这里的循环是对每一个ioqueue进行分配中断号,如果PCIE支持MSI-X那么使用MSIX中断,否则使用multi MSI 或者single MSI中断
⑤ nr_io_queues = vecs;
 dev->max_qid = nr_io_queues;//这里需要注意,如果创建的io queue的个数超过的中断的个数,那么应该按比例分配每个中断号对应几个io queuue,这里的重新赋值就是为这准备的
⑥queue_request_irq(dev, adminq, adminq->irqname);//重新打开admin queue的中断
⑦nvme_free_queues(dev, nr_io_queues + 1);//Free previously allocated queues that are no longer usable 
⑧nvme_assign_io_queues(dev);//将io queues 和 cpu绑定
            ①nvme_create_io_queues(dev);//创建IO Queue
 max = min(dev->max_qid, num_online_cpus());
 for (i = dev->queue_count; i <= max; i++) 
  if (!nvme_alloc_queue(dev, i, dev->q_depth, i - 1)) //参看前面的解释
   break; 
 max = min(dev->queue_count - 1, num_online_cpus()); 
 for (i = dev->online_queues; i <= max; i++) 
  if (nvme_create_queue(raw_nvmeq(dev, i), i)) 
   break; 
nvme_create_queue(raw_nvmeq(dev, i), i))解释
①adapter_alloc_cq(dev, qid, nvmeq);//通过nvme_submit_admin_cmd创建一个io completion queue
②adapter_alloc_sq(dev, qid, nvmeq);//通过 nvme_submit_admin_cmd 创建一个io submission queue
③queue_request_irq(dev, nvmeq, nvmeq->irqname);//注册irq
④nvme_init_queue(nvmeq, qid);//见前面的解释                   
           
            ②queues = min(dev->online_queues - 1, num_online_cpus());
                cpus_per_queue = num_online_cpus() / queues; //计算平均每次cpu绑定几个queue
                remainder = queues - (num_online_cpus() - queues * cpus_per_queue);//计算还有多少queue没有绑定cpu
            ③alloc_cpumask_var(&unassigned_cpus, GFP_KERNEL)//开辟cpumask
            ④cpumask_copy(unassigned_cpus, cpu_online_mask);
                cpu = cpumask_first(unassigned_cpus);//get the first cpu in a cpumask
            ⑤for (i = 1; i <= queues; i++) //这个for循环的作用是,为每一个queue绑定一个或多个cpu
                    nvme_set_queue_cpus(&mask, nvmeq, cpus_per_queue);//执行for_each_cpu(cpu, qmask), *per_cpu_ptr(nvmeq->dev->io_queue, cpu) = nvmeq->qid
                      后面会计算cpu的亲和度,为每个queue分配一个cpu


至此 setup ioqueue介绍完了

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
`nvme_submit_user_cmd()` 函数NVMe 驱动中用于向 NVMe 设备提交用户命令的函数。该函数的实现如下: ```c int nvme_submit_user_cmd(struct request_queue *q, struct nvme_command *cmd, void __user *ubuf, void __user *meta, unsigned timeout) { ... struct nvme_ns *ns = q->queuedata; ... struct nvme_user_io io = { .opcode = cmd->common.opcode, .flags = cmd->common.flags, .control = cpu_to_le16((timeout ? NVME_IO_FLAGS_PRACT : 0) | NVME_IO_FLAGS_CQ_UPDATE | NVME_IO_FLAGS_SGL_METABUF), .metadata = (__u64)meta, .addr = (__u64)ubuf, .slba = cpu_to_le64(cmd->rw.slba), .nlb = cpu_to_le16(cmd->rw.nblocks), .dsmgmt = cpu_to_le16(cmd->rw.dsmgmt), .reftag = cpu_to_le16(cmd->rw.reftag), .apptag = cpu_to_le16(cmd->rw.apptag), .appmask = cpu_to_le16(cmd->rw.appmask), }; ... ret = nvme_submit_user_cmd_hw(q, ns, &io, &cmd->common, timeout); ... return ret; } ``` 该函数的主要作用是将用户命令转换为 `nvme_user_io` 结构体,并调用 `nvme_submit_user_cmd_hw()` 函数将该命令提交给 NVMe 设备。下面是对该函数的参数及关键代码进行分析: - `q`:请求队列指针,用于指定 NVMe 设备所在的请求队列。 - `cmd`:NVMe 命令结构体指针,包含了要提交的 NVMe 写入命令的相关信息。 - `ubuf`:用户数据缓冲区的指针,该缓冲区包含了要写入存储介质的数据。 - `meta`:元数据缓冲区的指针,该缓冲区用于存储 NVMe 设备返回的写入操作结果。 - `timeout`:命令超时时间,以毫秒为单位。 该函数首先从请求队列中获取 NVMe 命名空间指针 `ns`,然后将用户命令转换为 `nvme_user_io` 结构体,并设置了一些命令的控制标志位。接着,该函数调用 `nvme_submit_user_cmd_hw()` 函数将命令提交给 NVMe 设备。 在 `nvme_submit_user_cmd_hw()` 函数中,NVMe 驱动会将 `nvme_user_io` 结构体中的数据转换为 NVMe 命令数据结构,并将该命令放入命令队列中。然后,NVMe 驱动会等待命令完成,并将命令的执行结果存储到元数据缓冲区中。最后,驱动程序会更新命令队列和完成队列的指针,并返回命令的执行状态。 在 NVMe 驱动中,`nvme_submit_user_cmd()` 函数是将用户命令提交给 NVMe 设备的入口函数,它的实现非常简单,主要是将用户命令转换为 NVMe 命令,并调用硬件相关的函数将命令提交给 NVMe 设备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值