diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c
index a946a879b9d6..31c484d51a69 100644
--- a/drivers/nvme/target/configfs.c
+++ b/drivers/nvme/target/configfs.c
@@ -2069,16 +2069,19 @@ static ssize_t nvmet_ctrl_enable_store(struct config_item *item,
 		ret = -EBUSY;
 		goto out_put_ctrl;
 	}
+	list_add_tail(&ctrl->port_entry, &port->static_ctrls);
 
 	ret = nvmet_enable_port(port);
 	if (ret)
-		goto out_put_ctrl;
+		goto out_del_entry;
 
 	conf->ctrl = ctrl;
 	up_read(&nvmet_config_sem);
 
 	return count;
 
+out_del_entry:
+	list_del(&ctrl->port_entry);
 out_put_ctrl:
 	up_read(&nvmet_config_sem);
 	nvmet_ctrl_put(ctrl);
@@ -2169,6 +2172,10 @@ static void nvmet_ctrl_release(struct config_item *item)
 		mod = conf->args.ops->owner;
 
 	if (conf->ctrl) {
+		down_write(&nvmet_config_sem);
+		list_del(&conf->ctrl->port_entry);
+		up_write(&nvmet_config_sem);
+
 		conf->args.ops->delete_ctrl(conf->ctrl);
 		nvmet_ctrl_put(conf->ctrl);
 	}
@@ -2401,6 +2408,7 @@ static struct config_group *nvmet_ports_make(struct config_group *group,
 
 	INIT_LIST_HEAD(&port->entry);
 	INIT_LIST_HEAD(&port->subsystems);
+	INIT_LIST_HEAD(&port->static_ctrls);
 	INIT_LIST_HEAD(&port->referrals);
 	port->inline_data_size = -1;	/* < 0 == let the transport choose */
 	port->max_queue_size = -1;	/* < 0 == let the transport choose */
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 1385368270de..6dab9d0f6b2f 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -1551,6 +1551,76 @@ static void nvmet_fatal_error_handler(struct work_struct *work)
 	ctrl->ops->delete_ctrl(ctrl);
 }
 
+/**
+ * nvmet_find_get_static_ctrl - find a static controller by port and cntlid
+ * @port: port to search under
+ * @trtype: transport type of the controller
+ * @cntlid: controller ID
+ *
+ * Returns: On success this returns a nvmet_ctrl with the refcount increased
+ * by one that the caller must drop.
+ */
+struct nvmet_ctrl *nvmet_find_get_static_ctrl(struct nvmet_port *port,
+					      int trtype, u16 cntlid)
+{
+	struct nvmet_ctrl *ctrl;
+
+	down_read(&nvmet_config_sem);
+
+	list_for_each_entry(ctrl, &port->static_ctrls, port_entry) {
+		if (ctrl->ops->type != trtype)
+			continue;
+
+		if (ctrl->cntlid == cntlid) {
+			if (!kref_get_unless_zero(&ctrl->ref))
+				ctrl = NULL;
+
+			up_read(&nvmet_config_sem);
+			return ctrl;
+		}
+	}
+
+	up_read(&nvmet_config_sem);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(nvmet_find_get_static_ctrl);
+
+/**
+ * nvmet_for_each_static_ctrl - execute fn over matching static controllers
+ * @port: port that the controller is using.
+ * @trtype: transport type of the controller.
+ * @fn: function to execute on each matching controller.
+ * @priv: driver specific struct passed to fn.
+ *
+ * This must be called with the nvmet_config_sem.
+ *
+ * Returns: passes fn's return value to caller.
+ */
+int nvmet_for_each_static_ctrl(struct nvmet_port *port, int trtype,
+			       nvmet_ctlr_iter_fn *fn, void *priv)
+{
+	struct nvmet_ctrl *ctrl;
+	int ret = 0;
+
+	lockdep_assert_held(&nvmet_config_sem);
+
+	list_for_each_entry(ctrl, &port->static_ctrls, port_entry) {
+		if (ctrl->ops->type != trtype)
+			continue;
+
+		if (!kref_get_unless_zero(&ctrl->ref))
+			continue;
+
+		ret = fn(priv, port, ctrl);
+		nvmet_ctrl_put(ctrl);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(nvmet_for_each_static_ctrl);
+
 struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
 {
 	struct nvmet_subsys *subsys;
@@ -1599,6 +1669,7 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
 
 	INIT_WORK(&ctrl->async_event_work, nvmet_async_event_work);
 	INIT_LIST_HEAD(&ctrl->async_events);
+	INIT_LIST_HEAD(&ctrl->port_entry);
 	INIT_RADIX_TREE(&ctrl->p2p_ns_map, GFP_KERNEL);
 	INIT_WORK(&ctrl->fatal_err_work, nvmet_fatal_error_handler);
 	INIT_DELAYED_WORK(&ctrl->ka_work, nvmet_keep_alive_timer);
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 2b0e624b80e1..a16d1c74e3d9 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -196,6 +196,7 @@ struct nvmet_port {
 	struct config_group		group;
 	struct config_group		subsys_group;
 	struct list_head		subsystems;
+	struct list_head		static_ctrls;
 	struct config_group		referrals_group;
 	struct list_head		referrals;
 	struct list_head		global_entry;
@@ -268,6 +269,7 @@ struct nvmet_ctrl {
 	struct list_head	async_events;
 	struct work_struct	async_event_work;
 
+	struct list_head	port_entry;
 	struct list_head	subsys_entry;
 	struct kref		ref;
 	struct delayed_work	ka_work;
@@ -603,6 +605,12 @@ struct nvmet_alloc_ctrl_args {
 	u16			status;
 };
 
+typedef int (nvmet_ctlr_iter_fn)(void *priv, struct nvmet_port *port,
+				 struct nvmet_ctrl *ctrl);
+int nvmet_for_each_static_ctrl(struct nvmet_port *port, int trtype,
+			       nvmet_ctlr_iter_fn *fn, void *priv);
+struct nvmet_ctrl *nvmet_find_get_static_ctrl(struct nvmet_port *port,
+					      int trtype, u16 cntlid);
 struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args);
 struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
 				       const char *hostnqn, u16 cntlid,
