NVMe Linux驱动系列二:target端[core.c]<16>

nvmet_cc_*


static inline bool nvmet_cc_en(u32 cc)
{
	return (cc >> NVME_CC_EN_SHIFT) & 0x1;
}

static inline u8 nvmet_cc_css(u32 cc)
{
	return (cc >> NVME_CC_CSS_SHIFT) & 0x7;
}

static inline u8 nvmet_cc_mps(u32 cc)
{
	return (cc >> NVME_CC_MPS_SHIFT) & 0xf;
}

static inline u8 nvmet_cc_ams(u32 cc)
{
	return (cc >> NVME_CC_AMS_SHIFT) & 0x7;
}

static inline u8 nvmet_cc_shn(u32 cc)
{
	return (cc >> NVME_CC_SHN_SHIFT) & 0x3;
}

static inline u8 nvmet_cc_iosqes(u32 cc)
{
	return (cc >> NVME_CC_IOSQES_SHIFT) & 0xf;
}

static inline u8 nvmet_cc_iocqes(u32 cc)
{
	return (cc >> NVME_CC_IOCQES_SHIFT) & 0xf;
}

static inline bool nvmet_css_supported(u8 cc_css)
{
	switch (cc_css << NVME_CC_CSS_SHIFT) {
	case NVME_CC_CSS_NVM:
	case NVME_CC_CSS_CSI:
		return true;
	default:
		return false;
	}
}

这一系列的函数和宏用于操作和解析 NVMe 控制器配置寄存器(Controller Configuration Register,CC)的不同字段。这些字段包括:

  • nvmet_cc_en: 用于判断控制器是否启用(Enabled)。
  • nvmet_cc_css: 用于获取控制器状态(Controller State)。
  • nvmet_cc_mps: 用于获取控制器最大命令页大小(Maximum Page Size)。
  • nvmet_cc_ams: 用于获取控制器管理队列特性(Admin Queue Management Scheme)。
  • nvmet_cc_shn: 用于获取控制器热插拔通知状态(Shutdown Notification)。
  • nvmet_cc_iosqes: 用于获取控制器支持的I/O队列元素大小(I/O Submission Queue Element Size)。
  • nvmet_cc_iocqes: 用于获取控制器支持的I/O完成队列元素大小(I/O Completion Queue Element Size)。
  • nvmet_css_supported: 用于判断给定的控制器状态是否为支持的状态。

这些函数和宏用于根据给定的 CC 寄存器的值解析出不同的配置信息。例如,nvmet_cc_en 会检查控制器是否启用,nvmet_cc_css 会获取控制器的状态,nvmet_cc_mps 会获取控制器支持的最大命令页大小等等。而 nvmet_css_supported 则判断给定的控制器状态是否为支持的状态。

这些函数和宏的目的是为了方便解析和操作 NVMe 控制器配置寄存器的各个字段,以便在驱动代码中进行配置和控制。

nvmet_start_ctrl



static void nvmet_start_ctrl(struct nvmet_ctrl *ctrl)
{
	lockdep_assert_held(&ctrl->lock);

	/*
	 * Only I/O controllers should verify iosqes,iocqes.
	 * Strictly speaking, the spec says a discovery controller
	 * should verify iosqes,iocqes are zeroed, however that
	 * would break backwards compatibility, so don't enforce it.
	 */
	if (!nvmet_is_disc_subsys(ctrl->subsys) &&
	    (nvmet_cc_iosqes(ctrl->cc) != NVME_NVM_IOSQES ||
	     nvmet_cc_iocqes(ctrl->cc) != NVME_NVM_IOCQES)) {
		ctrl->csts = NVME_CSTS_CFS;
		return;
	}

	if (nvmet_cc_mps(ctrl->cc) != 0 ||
	    nvmet_cc_ams(ctrl->cc) != 0 ||
	    !nvmet_css_supported(nvmet_cc_css(ctrl->cc))) {
		ctrl->csts = NVME_CSTS_CFS;
		return;
	}

	ctrl->csts = NVME_CSTS_RDY;

	/*
	 * Controllers that are not yet enabled should not really enforce the
	 * keep alive timeout, but we still want to track a timeout and cleanup
	 * in case a host died before it enabled the controller.  Hence, simply
	 * reset the keep alive timer when the controller is enabled.
	 */
	if (ctrl->kato)
		mod_delayed_work(nvmet_wq, &ctrl->ka_work, ctrl->kato * HZ);
}

这段代码用于启动一个 NVMe 控制器(Controller)。下面是代码的主要逻辑:

  1. 首先,它会使用 lockdep_assert_held 来断言当前是否持有控制器的锁。这可以用于确保只有在持有控制器锁的情况下才能调用这个函数。

  2. 接下来,它会检查控制器的 I/O队列元素大小(iosqesiocqes)是否与NVMe规范中定义的默认值(NVME_NVM_IOSQESNVME_NVM_IOCQES)相匹配。只有I/O控制器(不包括发现控制器)才需要验证这两个值。如果不匹配,则将控制器状态设置为 NVME_CSTS_CFS(Controller Fatal Status),表示控制器处于错误状态,然后函数返回。

  3. 接下来,它会检查控制器的最大命令页大小(mps)、管理队列特性(ams)以及控制器状态(cc_css)是否满足一些特定的要求。如果不满足,同样将控制器状态设置为 NVME_CSTS_CFS。

  4. 如果前面的检查都通过了,那么将控制器状态设置为 NVME_CSTS_RDY(Controller Ready)。

  5. 最后,如果控制器的保持活动超时(kato)不为零,则会通过调度延迟工作 ka_work 来启动保持活动定时器,以便在超时后处理保持活动超时的情况。

总之,这段代码确保了在启动控制器时进行了一系列的检查,以确保控制器的配置和状态满足一些必要的要求,然后根据检查结果设置相应的控制器状态。

nvmet_clear_ctrl


static void nvmet_clear_ctrl(struct nvmet_ctrl *ctrl)
{
	lockdep_assert_held(&ctrl->lock);

	/* XXX: tear down queues? */
	ctrl->csts &= ~NVME_CSTS_RDY;
	ctrl->cc = 0;
}

这段代码用于清除一个 NVMe 控制器的配置和状态,以便在禁用或释放控制器时执行清理操作。下面是代码的主要逻辑:

  1. 首先,它会使用 lockdep_assert_held 来断言当前是否持有控制器的锁。这可以用于确保只有在持有控制器锁的情况下才能调用这个函数。

  2. 然后,它会将控制器状态寄存器(csts)的 NVME_CSTS_RDY 位清除,将控制器状态设置为未就绪状态。这表明控制器不再处于可用状态。

  3. 接下来,它会将控制器配置寄存器(cc)的值设置为零,即将所有配置字段重置为默认值。

总之,这段代码用于将控制器设置为未就绪状态并清除其配置,以便在禁用、释放或重置控制器时执行必要的清理操作。

nvmet_update_cc


void nvmet_update_cc(struct nvmet_ctrl *ctrl, u32 new)
{
	u32 old;

	mutex_lock(&ctrl->lock);
	old = ctrl->cc;
	ctrl->cc = new;

	if (nvmet_cc_en(new) && !nvmet_cc_en(old))
		nvmet_start_ctrl(ctrl);
	if (!nvmet_cc_en(new) && nvmet_cc_en(old))
		nvmet_clear_ctrl(ctrl);
	if (nvmet_cc_shn(new) && !nvmet_cc_shn(old)) {
		nvmet_clear_ctrl(ctrl);
		ctrl->csts |= NVME_CSTS_SHST_CMPLT;
	}
	if (!nvmet_cc_shn(new) && nvmet_cc_shn(old))
		ctrl->csts &= ~NVME_CSTS_SHST_CMPLT;
	mutex_unlock(&ctrl->lock);
}

这段代码用于更新 NVMe 控制器的控制寄存器(cc)的值,并在需要的情况下触发相应的操作。下面是代码的主要逻辑:

  1. 首先,它会获取当前持有控制器锁的互斥锁。

  2. 接着,它会保存当前控制器的控制寄存器值(old)。

  3. 然后,它将控制器的控制寄存器值更新为新的值(new)。

  4. 接下来,它检查是否需要启动控制器。如果新的控制寄存器值中的 EN(Enable)位被设置,并且旧的控制寄存器值中的 EN 位未设置,则调用 nvmet_start_ctrl 函数来启动控制器。

  5. 然后,它检查是否需要禁用控制器。如果新的控制寄存器值中的 EN 位未设置,并且旧的控制寄存器值中的 EN 位被设置,则调用 nvmet_clear_ctrl 函数来禁用控制器。

  6. 接着,它检查是否需要触发控制器的 SHN(Shutdown Notification)功能。如果新的控制寄存器值中的 SHN 位被设置,并且旧的控制寄存器值中的 SHN 位未设置,则调用 nvmet_clear_ctrl 函数来禁用控制器,并将控制器状态寄存器(csts)的 SHST_CMPLT 位设置为完成状态。

  7. 最后,它会解锁控制器的互斥锁。

总之,这段代码用于更新控制器的控制寄存器值,并在控制器状态发生变化时触发相应的操作,例如启动、禁用控制器或触发控制器的关闭通知功能。

nvmet_init_cap


static void nvmet_init_cap(struct nvmet_ctrl *ctrl)
{
	/* command sets supported: NVMe command set: */
	ctrl->cap = (1ULL << 37);
	/* Controller supports one or more I/O Command Sets */
	ctrl->cap |= (1ULL << 43);
	/* CC.EN timeout in 500msec units: */
	ctrl->cap |= (15ULL << 24);
	/* maximum queue entries supported: */
	if (ctrl->ops->get_max_queue_size)
		ctrl->cap |= ctrl->ops->get_max_queue_size(ctrl) - 1;
	else
		ctrl->cap |= NVMET_QUEUE_SIZE - 1;

	if (nvmet_is_passthru_subsys(ctrl->subsys))
		nvmet_passthrough_override_cap(ctrl);
}

这段代码用于初始化 NVMe 控制器的能力寄存器(cap)的值。以下是代码的主要逻辑:

  1. 首先,它设置控制器支持的命令集。在这里,它使用位运算将位 37 设置为 1,表示支持 NVMe 命令集。然后,它将位 43 设置为 1,表示控制器支持一个或多个 I/O 命令集。

  2. 接下来,它设置 CC.EN(控制器使能)的超时时间,以 500 毫秒为单位。它将位 24-27 设置为 15,表示超时时间为 15 * 500 毫秒。

  3. 然后,它设置控制器支持的最大队列条目数。如果控制器的操作集中有一个 get_max_queue_size 函数,则它调用该函数获取最大队列大小,并将其减去 1 设置到能力寄存器。否则,它将默认的队列大小(NVMET_QUEUE_SIZE)减去 1 设置到能力寄存器。

  4. 最后,如果控制器是一个传递命令的子系统(passthrough subsystem),则调用 nvmet_passthrough_override_cap 函数进行一些特定的能力设置。

总之,这段代码用于初始化控制器的能力寄存器的值,包括支持的命令集、超时时间、最大队列条目数等信息。

nvmet_ctrl_find_get


struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
				       const char *hostnqn, u16 cntlid,
				       struct nvmet_req *req)
{
	struct nvmet_ctrl *ctrl = NULL;
	struct nvmet_subsys *subsys;

	subsys = nvmet_find_get_subsys(req->port, subsysnqn);
	if (!subsys) {
		pr_warn("connect request for invalid subsystem %s!\n",
			subsysnqn);
		req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
		goto out;
	}

	mutex_lock(&subsys->lock);
	list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
		if (ctrl->cntlid == cntlid) {
			if (strncmp(hostnqn, ctrl->hostnqn, NVMF_NQN_SIZE)) {
				pr_warn("hostnqn mismatch.\n");
				continue;
			}
			if (!kref_get_unless_zero(&ctrl->ref))
				continue;

			/* ctrl found */
			goto found;
		}
	}

	ctrl = NULL; /* ctrl not found */
	pr_warn("could not find controller %d for subsys %s / host %s\n",
		cntlid, subsysnqn, hostnqn);
	req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);

found:
	mutex_unlock(&subsys->lock);
	nvmet_subsys_put(subsys);
out:
	return ctrl;
}

这段代码用于查找并获取一个 NVMe 控制器对象。以下是代码的主要逻辑:

  1. 首先,通过给定的子系统名称(subsysnqn)调用 nvmet_find_get_subsys 函数来获取对应的子系统对象指针。如果子系统不存在,则会输出警告信息,将错误信息存储到请求的 CQE(Command Queue Entry) 结构中,然后跳转到 out 标签处。

  2. 接着,获取子系统的互斥锁,以确保对子系统对象的访问是线程安全的。

  3. 使用 list_for_each_entry 循环遍历子系统对象中的控制器列表。对于每个控制器,它会检查其 cntlid 是否与给定的 cntlid 相匹配。如果匹配,它会继续检查控制器的 hostnqn 是否与给定的 hostnqn 相匹配。如果匹配,它还会尝试递增控制器的引用计数,以确保控制器对象不会在函数结束前被释放。

  4. 如果找到了匹配的控制器,它会跳转到 found 标签处,解锁子系统的互斥锁,然后将对子系统的引用计数减少。

  5. 如果没有找到匹配的控制器,它会输出警告信息,将错误信息存储到请求的 CQE 结构中,然后跳转到 out 标签处。

  6. 如果找到了匹配的控制器,那么在 found 标签后,返回控制器对象指针。

总之,这段代码用于根据给定的子系统名称、主机名称和控制器 ID 来查找并获取对应的 NVMe 控制器对象。如果找到匹配的控制器,它会返回控制器对象的指针。如果找不到匹配的控制器或者出现错误,它会输出警告信息并返回 NULL。

nvmet_check_ctrl_status


u16 nvmet_check_ctrl_status(struct nvmet_req *req)
{
	if (unlikely(!(req->sq->ctrl->cc & NVME_CC_ENABLE))) {
		pr_err("got cmd %d while CC.EN == 0 on qid = %d\n",
		       req->cmd->common.opcode, req->sq->qid);
		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;
	}

	if (unlikely(!(req->sq->ctrl->csts & NVME_CSTS_RDY))) {
		pr_err("got cmd %d while CSTS.RDY == 0 on qid = %d\n",
		       req->cmd->common.opcode, req->sq->qid);
		return NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR;
	}

	if (unlikely(!nvmet_check_auth_status(req))) {
		pr_warn("qid %d not authenticated\n", req->sq->qid);
		return NVME_SC_AUTH_REQUIRED | NVME_SC_DNR;
	}
	return 0;
}

这段代码用于检查 NVMe 控制器的状态以及授权状态,以确保可以执行请求的命令。以下是代码的主要逻辑:

  1. 首先,它检查控制器的 cc 寄存器是否设置了 NVME_CC_ENABLE 标志位。如果未设置该标志位(即 CC.EN 为0),表示控制器未启用,它会输出错误信息指示收到的命令类型以及在哪个队列中(qid)收到了命令。然后返回 NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR,表示命令序列错误和不可重试错误。

  2. 接着,它检查控制器的 csts 寄存器是否设置了 NVME_CSTS_RDY 标志位。如果未设置该标志位(即 CSTS.RDY 为0),表示控制器未就绪,它会输出错误信息指示收到的命令类型以及在哪个队列中(qid)收到了命令。然后返回 NVME_SC_CMD_SEQ_ERROR | NVME_SC_DNR,表示命令序列错误和不可重试错误。

  3. 然后,它调用 nvmet_check_auth_status 函数检查请求的授权状态。如果授权状态不符合要求,它会输出警告信息指示收到的命令类型以及在哪个队列中(qid)收到了命令。然后返回 NVME_SC_AUTH_REQUIRED | NVME_SC_DNR,表示需要授权且不可重试错误。

  4. 如果以上条件都满足,即控制器处于启用状态、就绪状态,并且授权状态正常,它会返回0,表示命令可以继续执行。

总之,这段代码用于验证控制器是否处于合适的状态以及是否已经进行了授权,以便决定是否可以执行收到的 NVMe 命令。如果状态不符合要求,它会输出相应的错误或警告信息,并返回相应的错误码。

nvmet_host_allowed


bool nvmet_host_allowed(struct nvmet_subsys *subsys, const char *hostnqn)
{
	struct nvmet_host_link *p;

	lockdep_assert_held(&nvmet_config_sem);

	if (subsys->allow_any_host)
		return true;

	if (nvmet_is_disc_subsys(subsys)) /* allow all access to disc subsys */
		return true;

	list_for_each_entry(p, &subsys->hosts, entry) {
		if (!strcmp(nvmet_host_name(p->host), hostnqn))
			return true;
	}

	return false;
}

这段代码用于检查给定的主机名是否被允许访问特定的 NVMe 子系统。以下是代码的主要逻辑:

  1. 首先,它检查子系统是否允许任何主机访问。如果设置了子系统的 allow_any_host 标志,表示允许任何主机访问该子系统,它会返回 true

  2. 接着,它检查子系统是否为发现子系统(Discovery Subsystem)。对于发现子系统,它允许所有主机访问,因此会返回 true

  3. 如果以上条件都不满足,它会遍历子系统的主机链表,检查每个主机的主机名是否与给定的主机名匹配。如果找到匹配的主机名,表示该主机被允许访问子系统,它会返回 true

  4. 如果遍历完所有主机仍未找到匹配的主机名,表示该主机不被允许访问子系统,它会返回 false

总之,这段代码用于确定特定的主机名是否被允许访问给定的 NVMe 子系统。如果子系统允许任何主机访问,或者子系统是发现子系统,或者在主机链表中找到了匹配的主机名,它会返回 true。否则,它会返回 false,表示该主机名不被允许访问子系统。

nvmet_setup_p2p_ns_map


/*
 * Note: ctrl->subsys->lock should be held when calling this function
 */
static void nvmet_setup_p2p_ns_map(struct nvmet_ctrl *ctrl,
		struct nvmet_req *req)
{
	struct nvmet_ns *ns;
	unsigned long idx;

	if (!req->p2p_client)
		return;

	ctrl->p2p_client = get_device(req->p2p_client);

	xa_for_each(&ctrl->subsys->namespaces, idx, ns)
		nvmet_p2pmem_ns_add_p2p(ctrl, ns);
}

这段代码用于设置控制器的 P2P 命名空间映射(Peer-to-Peer Namespace Map)。以下是代码的主要逻辑:

  1. 首先,检查请求中是否包含 P2P 客户端设备。如果没有 P2P 客户端设备,表示不需要进行 P2P 命名空间映射,直接返回。

  2. 如果请求中存在 P2P 客户端设备,通过 get_device 函数增加 P2P 客户端设备的引用计数,以确保其在映射期间不会被释放。

  3. 遍历子系统的命名空间集合,对于每个命名空间,调用 nvmet_p2pmem_ns_add_p2p 函数,将 P2P 客户端设备与命名空间建立映射关系。

总之,这段代码在控制器的 P2P 命名空间映射中添加了一个新的 P2P 客户端设备,并遍历已存在的命名空间,为每个命名空间建立与该 P2P 客户端设备的映射关系。需要注意的是,在调用该函数之前,需要确保已经获取了子系统的锁,以避免并发访问问题。

nvmet_release_p2p_ns_map


/*
 * Note: ctrl->subsys->lock should be held when calling this function
 */
static void nvmet_release_p2p_ns_map(struct nvmet_ctrl *ctrl)
{
	struct radix_tree_iter iter;
	void __rcu **slot;

	radix_tree_for_each_slot(slot, &ctrl->p2p_ns_map, &iter, 0)
		pci_dev_put(radix_tree_deref_slot(slot));

	put_device(ctrl->p2p_client);
}

这段代码用于释放控制器的 P2P 命名空间映射(Peer-to-Peer Namespace Map)。以下是代码的主要逻辑:

  1. 首先,使用 radix_tree_iter 迭代器遍历 P2P 命名空间映射的 radix 树。通过 radix_tree_for_each_slot 宏来进行迭代,遍历每个槽位(slot)。

  2. 对于每个槽位,使用 radix_tree_deref_slot 函数解引用槽位的值,得到保存在槽位中的 P2P 设备指针,并调用 pci_dev_put 函数减少其引用计数,从而释放对该 P2P 设备的引用。

  3. 最后,使用 put_device 函数减少 P2P 客户端设备的引用计数,以释放对其的引用。

总之,这段代码通过遍历控制器的 P2P 命名空间映射,释放与之相关的 P2P 设备引用,同时也释放了 P2P 客户端设备的引用。需要注意的是,在调用该函数之前,需要确保已经获取了子系统的锁,以避免并发访问问题。

nvmet_fatal_error_handler


static void nvmet_fatal_error_handler(struct work_struct *work)
{
	struct nvmet_ctrl *ctrl =
			container_of(work, struct nvmet_ctrl, fatal_err_work);

	pr_err("ctrl %d fatal error occurred!\n", ctrl->cntlid);
	ctrl->ops->delete_ctrl(ctrl);
}

这段代码实现了一个用于处理 NVMe over Fabrics 控制器的致命错误的工作队列处理函数。以下是代码的主要逻辑:

  1. 首先,使用 container_of 宏将工作队列的成员指针转换为包含该成员的 struct nvmet_ctrl 控制器结构体指针。

  2. 在日志中记录控制器的临界错误(fatal error)。

  3. 调用控制器操作(ops)中的 delete_ctrl 函数,这个函数用于删除控制器。

总之,这段代码的目的是在控制器遇到严重错误时,通过工作队列异步处理,以便在不影响当前执行的情况下删除控制器。这样可以提高系统的稳定性和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值