Linux NVMe Driver学习笔记之9: nvme_reset_work压轴大戏
这篇文章紧接上回分解,在nvme_probe函数的最后一步调用nvme_reset_work进行reset操作,nvme_reset_work的主要工作可以概括如下几个步骤:
进入nvme_reset_work函数后先检查NVME_CTRL_RESETTING标志,来确保nvme_reset_work不会被重复进入。
调用nvme_pci_enable
调用nvme_configure_admin_queue
调用nvme_init_queue
调用nvme_alloc_admin_tags
调用nvme_init_identify
调用nvme_setup_io_queues
调用nvme_start_queues/nvme_dev_add之后,接着调用nvme_queue_scan
上篇文章中,我们解析了nvme_setup_io_queues内容,本文我们接着介绍nvme_reset_work中最后的三个函数。
我们来看看nvme_start_queues的代码:
void nvme_start_queues(struct nvme_ctrl *ctrl)
{
struct nvme_ns *ns;
mutex_lock(&ctrl->namespaces_mutex);
list_for_each_entry(ns, &ctrl->namespaces, list) {
blk_mq_start_stopped_hw_queues(ns->queue, true);
blk_mq_kick_requeue_list(ns->queue);
}
mutex_unlock(&ctrl->namespaces_mutex);
}
这个过程比较简单,主要是调用了blk_mq_start_stopped_hw_queues和blk_mq_kick_requeue_list去启动mq-block层的hardware queues和request queues. 有关mq-block层的内容,我们在之前的文章中已阐述,请参考:
接下来,我们看下比较关键的函数nvme_dev_add:
static int nvme_dev_add(struct nvme_dev *dev)
{
if (!dev->ctrl.tagset) {
dev->tagset.ops = &nvme_mq_ops;
dev->tagset.nr_hw_queues = dev->online_queues - 1;
dev->tagset.timeout = NVME_IO_TIMEOUT;
dev->tagset.numa_node = dev_to_node(dev->dev);
dev->tagset.queue_depth =
min_t(int, dev->q_depth, BLK_MQ_MAX_DEPTH) - 1;
dev->tagset.cmd_size = nvme_cmd_size(dev);
dev->tagset.flags = BLK_MQ_F_SHOULD_MERGE;
dev->tagset.driver_data = dev;
if (blk_mq_alloc_tag_set(&dev->tagset))
return 0;
dev->ctrl.tagset = &dev->tagset;
} else {
blk_mq_update_nr_hw_queues(&dev->tagset, dev->online_queues - 1);
/* Free previously allocated queues that are no longer usable */
nvme_free_queues(dev, dev->online_queues);
}
return 0;
}
从上面的代码显示,nvme_dev_add对tagset结构体初始化,接着调用blk_mq_alloc_tag_set分配tag set并与request queue关联,这一点与admin_tagset类似,这里不再展开,请参考:
我们再看一下最后一个函数nvme_queue_scan:
void nvme_queue_scan(struct nvme_ctrl *ctrl)
{
/*
* Do not queue new scan work when a controller is reset during
* removal.
*/
if (ctrl->state == NVME_CTRL_LIVE)
schedule_work(&ctrl->scan_work);
}
从上面代码可以发现,nvme_queue_scan最后调用的是ctrl->scan_work. 在之前nvme_init_ctrl函数(Linux NVMe Driver学习笔记之3:nvme_probe函数解析)中已经被赋值:
INIT_WORK(&ctrl->scan_work, nvme_scan_work);
那我们就看一下nvme_scan_work:
static void nvme_scan_work(struct work_struct *work)
{
struct nvme_ctrl *ctrl =
container_of(work, struct nvme_ctrl, scan_work);
struct nvme_id_ctrl *id;
unsigned nn;
if (ctrl->state != NVME_CTRL_LIVE)
return;
// 再次识别nvme controller
if (nvme_identify_ctrl(ctrl, &id))
return;
nn = le32_to_cpu(id->nn);
// 判断nvme version是否比1.1.0高,且没有设置NVME_QUIRK_IDENTIFY_CNS的话,就调用nvme_scan_ns_list
if (ctrl->vs >= NVME_VS(1, 1, 0) &&
!(ctrl->quirks & NVME_QUIRK_IDENTIFY_CNS)) {
if (!nvme_scan_ns_list(ctrl, nn))
goto done;
}
//检查namespace是否合法,去掉不合法的namespace
nvme_scan_ns_sequential(ctrl, nn);
done:
mutex_lock(&ctrl->namespaces_mutex);
list_sort(NULL, &ctrl->namespaces, ns_cmp);
mutex_unlock(&ctrl->namespaces_mutex);
kfree(id);
}
上面代码中主要是调用了两个scan namespace的函数:nvme_scan_ns_list和nvme_scan_ns_sequential。nvme_scan_ns_list,如果执行成功的话,返回0,因此不再执行nvme_scan_ns_sequential。nvme_scan_ns_sequential的作用就是检查namespace是否合法,去掉不合法的namespace。
在nvme_scan_work的最后,将所有找到的namespace通过list_sort(NULL, &ctrl->namespaces, ns_cmp)来排序.
static int ns_cmp(void *priv, struct list_head *a, struct list_head *b)
{
struct nvme_ns *nsa = container_of(a, struct nvme_ns, list);
struct nvme_ns *nsb = container_of(b, struct nvme_ns, list);
return nsa->ns_id - nsb->ns_id;
}
nvme_scan_work 就是在nvme_reset_work已经发现nvme controller的情况下,再次对这个nvme controller下面的进行扫描,因为namespace最多可以两级级联,每个nvme controller下的name space都是放在ctrl->namespaces 这个链表中,且是按照name space id的大小排序。
更多推荐
所有评论(0)