nvme_queue_scan 分析

nvme_reset_work 最后通过nvme_queue_scan(&dev->ctrl); 来scan controller
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);
}
可见执行的是ctrl->scan_work,而ctrl->scan_work的赋值是在nvme_init_ctrl中
    INIT_WORK(&ctrl->scan_work, nvme_scan_work);
继续看nvme_scan_work
static void nvme_scan_work(struct work_struct *work)
{
//通过container_of 通过work_struct得到nvme_ctrl
    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);
}
static int nvme_scan_ns_list(struct nvme_ctrl *ctrl, unsigned nn)
{
    struct nvme_ns *ns;
    __le32 *ns_list;
    unsigned i, j, nsid, prev = 0, num_lists = DIV_ROUND_UP(nn, 1024);
    int ret = 0;

    ns_list = kzalloc(0x1000, GFP_KERNEL);
    if (!ns_list)
        return -ENOMEM;
//便利0~num_lists 的nvme controller,通过nvme_identify_ns_list 来检查nvme controller,这里的num_lists 分别是0,1,等。而num_lists = DIV_ROUND_UP(nn, 1024);nn = le32_to_cpu(id->nn);也是村检测到的nvme controller 读到的。

    for (i = 0; i < num_lists; i++) {
//识别namespace 0的nvme controller,将结果存在ns_list,所以ns_list也是一个namespace
        ret = nvme_identify_ns_list(ctrl, prev, ns_list);
        if (ret)
            goto free;

        for (j = 0; j < min(nn, 1024U); j++) {
 //根据ns_list 中的数据不为0的话,就说明这个nsid有再使用,因此继续调用nvme_validate_ns 来申请gendisk,难道namespace 最多只能是两级?
            nsid = le32_to_cpu(ns_list[j]);
            if (!nsid)
                goto out;
//针对每一个nsid,调用nvme_validate_ns 来申请gendisk
            nvme_validate_ns(ctrl, nsid);

            while (++prev < nsid) {
            由于namespace是按照大小排序的,因此如果小于nsid找到nvme肯定是不正常的
                ns = nvme_find_get_ns(ctrl, prev);
                if (ns) {
                    nvme_ns_remove(ns);
                    nvme_put_ns(ns);
                }
            }
        }
        nn -= j;
    }
 out:
    nvme_remove_invalid_namespaces(ctrl, prev);
 free:
    kfree(ns_list);
    return ret;
}
这个函数nvme_scan_ns_list,如果执行成功的话,返回0,因此不再执行nvme_scan_work中的nvme_scan_ns_sequential。
在nvme_scan_work的最后,将所有找到且链接到ctrl->namespaces的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;
}
可见是通过ns_id的大小来排序的,因此nvme_scan_work 就是在nvme_reset_work 已经发现nvme controller的情况下,再次对这个nvme controller下面的进行扫描,因为namespace最多可以两级级联,每个nvme controller下的name space都是放在ctrl->namespaces 这个链表中,且是按照name space id的大小排序。

`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 设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值