Skip to contentSkip to conversation

nvme: ANATT handling

PATCH New Hannes Reinecke 3 participants 26 messages Jun 7, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover nvme: ANATT handling

Hi all,

this patchset implements ANA transition timeout handling for NVMe-oF. On the target side it implements a delayed workqueue for updating the ANA state, and on the host side a simple timer is started when either a namespace is found to be in ANA state change state or if an I/O command is returned with a status indicating a non-matching ANA state. If the timer expires the controller is reset.

Patches are relative to hchs block.git nvme-ana branch.

As usual, comments and reviews are welcome.

Hannes Reinecke (4):
nvmet: make ANATT configurable
nvmet: ANA transition timeout handling
nvme: ANA transition timeout handling
nvme: start ANATT timer on out-of-order state changes

 drivers/nvme/host/core.c        |  3 ++
 drivers/nvme/host/multipath.c   | 62 +++++++++++++++++++++++++++-----
 drivers/nvme/host/nvme.h        |  6 ++++
 drivers/nvme/target/admin-cmd.c |  2 +-
 drivers/nvme/target/configfs.c  | 39 ++++++++++++++++++++-
 drivers/nvme/target/core.c      | 78 +++++++++++++++++++++++++++++++++++++++++
 drivers/nvme/target/nvmet.h     | 14 ++++++++
 7 files changed, 193 insertions(+), 11 deletions(-)
Signature · 1 line
2.12.3
#1 lore raw

PATCH 1/4 nvmet: make ANATT configurable

Signed-off-by Hannes Reinecke <hare at suse.com>
Signed-off-by Christoph Hellwig <hch at lst.de>
---
 drivers/nvme/target/admin-cmd.c |  2 +-
 drivers/nvme/target/configfs.c  | 34 ++++++++++++++++++++++++++++++++++
 drivers/nvme/target/nvmet.h     |  2 ++
 3 files changed, 37 insertions(+), 1 deletion(-)
drivers/nvme/target/admin-cmd.c +1 -1
index 8de264ece826..a49e1435fa54 100644
@@ -315,7 +315,7 @@ static void nvmet_execute_identify_ctrl(struct nvmet_req *req)
 	id->msdbd = ctrl->ops->msdbd;
 
 	id->anacap = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
-	id->anatt = 10; /* random value */
+	id->anatt = req->port->anatt;
 	id->anagrpmax = cpu_to_le32(NVMET_MAX_ANAGRPS);
 	id->nanagrpid = cpu_to_le32(NVMET_MAX_ANAGRPS);
 
drivers/nvme/target/configfs.c +34 -0
index c5a770ac2f34..cca4de356818 100644
@@ -265,6 +265,38 @@ static ssize_t nvmet_addr_trtype_store(struct config_item *item,
 
 CONFIGFS_ATTR(nvmet_, addr_trtype);
 
+static ssize_t nvmet_anatt_show(struct config_item *item,
+		char *page)
+{
+	struct nvmet_port *port = to_nvmet_port(item);
+
+	return snprintf(page, PAGE_SIZE, "%d\n", port->anatt);
+}
+
+static ssize_t nvmet_anatt_store(struct config_item *item,
+		const char *page, size_t count)
+{
+	struct nvmet_port *port = to_nvmet_port(item);
+	int ret;
+	u8 ana_tt;
+
+	ret = kstrtou8(page, 0, &ana_tt);
+	if (ret || ana_tt == 0)
+		return -EINVAL;
+
+	if (port->enabled) {
+		pr_err("Cannot modify ANA transition timeout while enabled\n");
+		return -EACCES;
+	}
+	down_write(&nvmet_config_sem);
+	port->anatt = ana_tt;
+	up_write(&nvmet_config_sem);
+
+	return count;
+}
+
+CONFIGFS_ATTR(nvmet_, anatt);
+
 /*
  * Namespace structures & file operation functions below
  */
@@ -1033,6 +1065,7 @@ static struct configfs_attribute *nvmet_port_attrs[] = {
 	&nvmet_attr_addr_traddr,
 	&nvmet_attr_addr_trsvcid,
 	&nvmet_attr_addr_trtype,
+	&nvmet_attr_anatt,
 	NULL,
 };
 
@@ -1077,6 +1110,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->referrals);
+	port->anatt = NVMET_DEFAULT_ANATT;
 
 	port->disc_addr.portid = cpu_to_le16(portid);
 	config_group_init_type_name(&port->group, name, &nvmet_port_type);
drivers/nvme/target/nvmet.h +2 -0
index 1f562732baf3..948209d5f803 100644
@@ -132,6 +132,7 @@ struct nvmet_port {
 	enum nvme_ana_state		*ana_state;
 	void				*priv;
 	bool				enabled;
+	u8				anatt;
 };
 
 static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
@@ -399,6 +400,7 @@ u32 nvmet_get_log_page_len(struct nvme_command *cmd);
  */
 #define NVMET_MAX_ANAGRPS	128
 #define NVMET_DEFAULT_ANA_GRPID	1
+#define NVMET_DEFAULT_ANATT	10
 
 #define NVMET_KAS		10
 #define NVMET_DISC_KATO		120
Signature · 1 line
2.12.3
#2 lore raw

PATCH 1/4 nvmet: make ANATT configurable

Needs a description. Especially to explain why you want it per-port. In the end the transition timeout is something determined by the failover backend, which in no way maps to a port. In fact it doesn't map to anything, which is why I would suggest to just make it global. This still won't cover the case of using different backed storage types with different failover characteristics, but at least it ?sn't too confusing.

drivers/nvme/target/configfs.c static ssize_t nvmet_addr_trtype_store(struct config_item *item, · line 285

Quoted 3 lines
drivers/nvme/target/configfs.c +3 -0
+	ret = kstrtou8(page, 0, &ana_tt);
+	if (ret || ana_tt == 0)
+		return -EINVAL;

Please propagate the actual error returned by kstrtou8.

drivers/nvme/target/configfs.c static ssize_t nvmet_addr_trtype_store(struct config_item *item, · line 293

Quoted 8 lines
drivers/nvme/target/configfs.c +8 -0
+
+	if (port->enabled) {
+		pr_err("Cannot modify ANA transition timeout while enabled\n");
+		return -EACCES;
+	}
+	down_write(&nvmet_config_sem);
+	port->anatt = ana_tt;
+	up_write(&nvmet_config_sem);

The enabled check needs to be under nvmet_config_sem to be race free.

#3 lore raw

PATCH 1/4 nvmet: make ANATT configurable

Quoted 8 lines · On Thu, 7 Jun 2018 14:06:12 +0200 Christoph Hellwig <[email protected]> wrote:

Needs a description. Especially to explain why you want it per-port. In the end the transition timeout is something determined by the failover backend, which in no way maps to a port. In fact it doesn't map to anything, which is why I would suggest to just make it global. This still won't cover the case of using different backed storage types with different failover characteristics, but at least it ?sn't too confusing.

Well, I just moved it to the position where the state change actually happend, and figured it'll give us the most flexibility.

Quoted 6 lines

drivers/nvme/target/configfs.c static ssize_t nvmet_addr_trtype_store(struct config_item *item, · line 285

Quoted 3 lines
drivers/nvme/target/configfs.c +3 -0
+	ret = kstrtou8(page, 0, &ana_tt);
+	if (ret || ana_tt == 0)
+		return -EINVAL;  

Please propagate the actual error returned by kstrtou8.

Okay.

Quoted 12 lines

drivers/nvme/target/configfs.c static ssize_t nvmet_addr_trtype_store(struct config_item *item, · line 293

Quoted 9 lines
drivers/nvme/target/configfs.c +3 -0
+
+	if (port->enabled) {
+		pr_err("Cannot modify ANA transition timeout while

enabled\n");

drivers/nvme/target/configfs.c +5 -0
+		return -EACCES;
+	}
+	down_write(&nvmet_config_sem);
+	port->anatt = ana_tt;
+	up_write(&nvmet_config_sem);  

The enabled check needs to be under nvmet_config_sem to be race free.

Okay, will be doing so.

Cheers,

Hannes

#5 lore raw

PATCH 2/4 nvmet: ANA transition timeout handling

In reply to Hannes Reinecke

Whenever an ANA state change is triggered the ANA state for that group ID is set to 'state change' and a delayed work is started, which will be setting the port to the actual state after anatt has expired.

Signed-off-by Hannes Reinecke <hare at suse.com>
---
 drivers/nvme/target/configfs.c |  5 ++-
 drivers/nvme/target/core.c     | 78 ++++++++++++++++++++++++++++++++++++++++++
 drivers/nvme/target/nvmet.h    | 12 +++++++
 3 files changed, 94 insertions(+), 1 deletion(-)
drivers/nvme/target/configfs.c +4 -1
index cca4de356818..a39e35399dba 100644
@@ -967,7 +967,9 @@ static ssize_t nvmet_ana_group_ana_state_store(struct config_item *item,
 	nvmet_ana_chgcnt++;
 	up_write(&nvmet_ana_sem);
 
-	nvmet_port_send_ana_event(grp->port);
+	nvmet_port_ana_state_change(grp->port, grp->grpid,
+				    nvmet_ana_state_names[i].state);
+
 	return count;
 }
 
@@ -1110,6 +1112,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->referrals);
+	INIT_LIST_HEAD(&port->anatt_list);
 	port->anatt = NVMET_DEFAULT_ANATT;
 
 	port->disc_addr.portid = cpu_to_le16(portid);
drivers/nvme/target/core.c +78 -0

119 lines not on this page · raw message

drivers/nvme/target/nvmet.h +12 -0
index 948209d5f803..a5a309ca003a 100644
@@ -129,6 +129,8 @@ struct nvmet_port {
 	struct list_head		referrals;
 	struct config_group		ana_groups_group;
 	struct nvmet_ana_group		ana_group1;
+	struct list_head		anatt_list;
+	struct mutex			anatt_list_lock;
 	enum nvme_ana_state		*ana_state;
 	void				*priv;
 	bool				enabled;
@@ -320,6 +322,14 @@ struct nvmet_async_event {
 	u8			log_page;
 };
 
+struct nvmet_ana_change_event {
+	struct delayed_work	work;
+	struct list_head	entry;
+	struct nvmet_port	*port;
+	enum nvme_ana_state	state;
+	u8			grpid;
+};
+
 u16 nvmet_parse_connect_cmd(struct nvmet_req *req);
 u16 nvmet_bdev_parse_io_cmd(struct nvmet_req *req);
 u16 nvmet_file_parse_io_cmd(struct nvmet_req *req);
@@ -364,6 +374,8 @@ void nvmet_ns_free(struct nvmet_ns *ns);
 
 void nvmet_send_ana_event(struct nvmet_subsys *subsys);
 void nvmet_port_send_ana_event(struct nvmet_port *port);
+void nvmet_port_ana_state_change(struct nvmet_port *port,
+				 u32 grpid, enum nvme_ana_state state);
 
 int nvmet_register_transport(const struct nvmet_fabrics_ops *ops);
 void nvmet_unregister_transport(const struct nvmet_fabrics_ops *ops);
Signature · 1 line
2.12.3
#6 lore raw

PATCH 2/4 nvmet: ANA transition timeout handling

Quoted 4 lines · Hannes Reinecke

Whenever an ANA state change is triggered the ANA state for that group ID is set to 'state change' and a delayed work is started, which will be setting the port to the actual state after anatt has expired.

What is the point? We want to move to the actual state as soon as we can, where as soon is defined by a backend. So we need manual control over exact states, including change state.

Even for testing it makes much more sense to have entirely manual control, so that your userspace test framework can trivial inject too long transition faults.

#7 lore raw

PATCH 2/4 nvmet: ANA transition timeout handling

Quoted 6 lines · On Thu, 7 Jun 2018 14:07:46 +0200 Christoph Hellwig <[email protected]> wrote:
Quoted 4 lines · Hannes Reinecke

Whenever an ANA state change is triggered the ANA state for that group ID is set to 'state change' and a delayed work is started, which will be setting the port to the actual state after anatt has expired.

Quoted 8 lines

What is the point? We want to move to the actual state as soon as we can, where as soon is defined by a backend. So we need manual control over exact states, including change state.

Even for testing it makes much more sense to have entirely manual control, so that your userspace test framework can trivial inject too long transition faults.

But we would _require_ userspace interaction to facilitate correct operation. And with a purely manual configuration on the target any ANATT value becomes meaningless as we have no way of enforcing it.

And with that patch I don't need any userspace framework; just updating the state is enough. And I even can set ANATT to 1 to have the state update done directly.

Hannes

#8 lore raw

PATCH 2/4 nvmet: ANA transition timeout handling

Quoted 3 lines · Hannes Reinecke

But we would _require_ userspace interaction to facilitate correct operation. And with a purely manual configuration on the target any ANATT value becomes meaningless as we have no way of enforcing it.

? The whole change state is entirely optional. For 90% of the users you'll never enter it. And when you need it, we need to control when to enter and exit it. The timer is of absolutely no use.

maintainer
#9 lore raw

PATCH 2/4 nvmet: ANA transition timeout handling

In reply to Hannes Reinecke
Quoted 4 lines
drivers/nvme/target/core.c +3 -0
+	INIT_DELAYED_WORK(&ev->work, nvmet_ana_state_change_work);
+	ev->port = port;
+	ev->grpid = grpid;

drivers/nvme/target/core.c void nvmet_port_send_ana_event(struct nvmet_port *port) · line 277

Quoted 10 lines
drivers/nvme/target/core.c +10 -0
+	ev->state = state;
+	mutex_lock(&port->anatt_list_lock);
+	list_add_tail(&ev->entry, &port->anatt_list);
+	mutex_unlock(&port->anatt_list_lock);
+	/*
+	 * Reduce the delay by 1 sec to not accidentally trigger an
+	 * ANA transition timeout failure on the host.
+	 */
+	queue_delayed_work(nvmet_ana_wq, &ev->work,
+			   (port->anatt - 1) * HZ);

hmm, what is that giving us?

Quoted 11 lines
drivers/nvme/target/core.c +3 -0
+}
+
  int nvmet_register_transport(const struct nvmet_fabrics_ops *ops)
  {
  	int ret = 0;
@@ -276,11 +336,22 @@ int nvmet_enable_port(struct nvmet_port *port)
  void nvmet_disable_port(struct nvmet_port *port)
  {
  	const struct nvmet_fabrics_ops *ops;
+	struct nvmet_ana_change_event *ev, *tmp;
 

drivers/nvme/target/core.c int nvmet_enable_port(struct nvmet_port *port) · line 350

Quoted 10 lines
drivers/nvme/target/core.c +6 -0
  	lockdep_assert_held(&nvmet_config_sem);
 
  	port->enabled = false;
 
+	mutex_lock(&port->anatt_list_lock);
+	list_for_each_entry_safe(ev, tmp, &port->anatt_list, entry) {
+		if (ev->port == port) {
+			list_del_init(&ev->entry);
+			cancel_delayed_work_sync(&ev->work);
+			kfree(ev);

I'd splice to a local list and cancel+free outside the mutex.

Quoted 8 lines
drivers/nvme/target/core.c +4 -0
+		}
+	}
+	mutex_unlock(&port->anatt_list_lock);
+
  	ops = nvmet_transports[port->disc_addr.trtype];
  	ops->remove_port(port);
  	module_put(ops->owner);

drivers/nvme/target/core.c static int __init nvmet_init(void) · line 1240

Quoted 8 lines
drivers/nvme/target/core.c +5 -0
@@ -1162,6 +1233,11 @@ static int __init nvmet_init(void)
  {
  	int error;
 
+	nvmet_ana_wq = alloc_workqueue("nvmet_ana_wq",
+			WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
+	if (!nvmet_ana_wq)
+		return -ENOMEM;
+

Why a dedicated workqueue? and why a mem reclaim one?

whats wrong with system_wq/system_unbound_wq/system_long_wq?

#10 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

In reply to Hannes Reinecke

Add a timer for tracking ANA transition timeout, and reset the controller once the timeout expires.

Signed-off-by Hannes Reinecke <hare at suse.com>
---
 drivers/nvme/host/core.c      |  3 +++
 drivers/nvme/host/multipath.c | 34 ++++++++++++++++++++++++++++------
 drivers/nvme/host/nvme.h      |  6 ++++++
 3 files changed, 37 insertions(+), 6 deletions(-)
drivers/nvme/host/core.c +3 -0
index e62de51209b2..3b578ae62cb5 100644
@@ -2378,6 +2378,7 @@ int nvme_init_identify(struct nvme_ctrl *ctrl)
 	ctrl->kas = le16_to_cpu(id->kas);
 	ctrl->max_namespaces = le32_to_cpu(id->mnan);
 	ctrl->anacap = id->anacap;
+	ctrl->anatt = id->anatt;
 	ctrl->nanagrpid = le32_to_cpu(id->nanagrpid);
 	ctrl->anagrpmax = le32_to_cpu(id->anagrpmax);
 
@@ -3025,6 +3026,7 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid)
 	blk_queue_flag_set(QUEUE_FLAG_NONROT, ns->queue);
 	ns->queue->queuedata = ns;
 	ns->ctrl = ctrl;
+	timer_setup(&ns->anatt_timer, nvme_anatt_timedout, 0);
 
 	kref_init(&ns->kref);
 	ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */
@@ -3113,6 +3115,7 @@ static void nvme_ns_remove(struct nvme_ns *ns)
 			blk_integrity_unregister(ns->disk);
 	}
 
+	del_timer_sync(&ns->anatt_timer);
 	mutex_lock(&ns->ctrl->subsys->lock);
 	nvme_mpath_clear_current_path(ns);
 	list_del_rcu(&ns->siblings);
drivers/nvme/host/multipath.c +28 -6
index d30229a21c97..7a37f5959ad4 100644
@@ -62,13 +62,12 @@ void nvme_failover_req(struct request *req)
 	 */
 	switch (nvme_req(req)->status & 0x7ff) {
 	case NVME_SC_ANA_TRANSITION:
-		/*
-		 * XXX: We should verify the controller doesn't die on during
-		 * the transition.  But that means we per-group timeout from
-		 * when we first hit the change state, so this won't be
-		 * entirely trivial..
-		 */
 		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
+		if (!timer_pending(&ns->anatt_timer)) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}
 		break;
 	case NVME_SC_ANA_PERSISTENT_LOSS:
 		nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);
@@ -390,11 +389,34 @@ static int nvme_process_ana_log(struct nvme_ctrl *ctrl, bool groups_only)
 static void nvme_ana_work(struct work_struct *work)
 {
 	struct nvme_ctrl *ctrl = container_of(work, struct nvme_ctrl, ana_work);
+	struct nvme_ns *ns;
 
 	nvme_process_ana_log(ctrl, false);
+	down_write(&ctrl->namespaces_rwsem);
+	list_for_each_entry(ns, &ctrl->namespaces, list) {
+		enum nvme_ana_state state = nvme_ns_ana_state(ns);
+
+		if (timer_pending(&ns->anatt_timer) &&
+		    state != NVME_ANA_CHANGE)
+			del_timer_sync(&ns->anatt_timer);
+		if (!timer_pending(&ns->anatt_timer) &&
+		    state == NVME_ANA_CHANGE) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}
+	}
+	up_write(&ctrl->namespaces_rwsem);
 	nvme_kick_requeue_lists(ctrl);
 }
 
+void nvme_anatt_timedout(struct timer_list *t)
+{
+	struct nvme_ns *ns = from_timer(ns, t, anatt_timer);
+
+	nvme_reset_ctrl(ns->ctrl);
+}
+
 int nvme_configure_ana(struct nvme_ctrl *ctrl)
 {
 	int error;
drivers/nvme/host/nvme.h +6 -0
index be2585576bad..d059b5522993 100644
@@ -200,6 +200,7 @@ struct nvme_ctrl {
 
 	/* asymmetric namespace access: */
 	u8 anacap;
+	u8 anatt;
 	u32 anagrpmax;
 	u32 nanagrpid;
 	enum nvme_ana_state *ana_state;
@@ -301,6 +302,7 @@ struct nvme_ns {
 	struct nvm_dev *ndev;
 	struct kref kref;
 	struct nvme_ns_head *head;
+	struct timer_list anatt_timer;
 
 	int lba_shift;
 	u16 ms;
@@ -469,6 +471,7 @@ void nvme_mpath_add_disk(struct nvme_ns *ns);
 void nvme_mpath_remove_disk(struct nvme_ns_head *head);
 int nvme_configure_ana(struct nvme_ctrl *ctrl);
 void nvme_deconfigure_ana(struct nvme_ctrl *ctrl);
+void nvme_anatt_timedout(struct timer_list *t);
 
 static inline void nvme_mpath_clear_current_path(struct nvme_ns *ns)
 {
@@ -531,6 +534,9 @@ static inline int nvme_configure_ana(struct nvme_ctrl *ctrl)
 static inline void nvme_deconfigure_ana(struct nvme_ctrl *ctrl)
 {
 }
+void nvme_anatt_timedout(struct timer_list *t)
+{
+}
 #endif /* CONFIG_NVME_MULTIPATH */
 
 #ifdef CONFIG_NVM
Signature · 1 line
2.12.3
#11 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

drivers/nvme/host/core.c static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid) · line 3029

Quoted 1 line
drivers/nvme/host/core.c +1 -0
+	timer_setup(&ns->anatt_timer, nvme_anatt_timedout, 0);

ANATT is per-group, so having a timer in the namespace is wrong.

#12 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

Quoted 4 lines · On Thu, 7 Jun 2018 14:09:41 +0200 Christoph Hellwig <[email protected]> wrote:

drivers/nvme/host/core.c static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid) · line 3029

Quoted 1 line
drivers/nvme/host/core.c +1 -0
+	timer_setup(&ns->anatt_timer, nvme_anatt_timedout, 0);  

ANATT is per-group, so having a timer in the namespace is wrong.

But would requiring us to create a separate ana group structure on the host. Could be looking into that if you insist, but this implementation is way easier.

Cheers,

Hannes

#13 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

Quoted 11 lines · Hannes Reinecke
Quoted 4 lines · On Thu, 7 Jun 2018 14:09:41 +0200 Christoph Hellwig <[email protected]> wrote:

drivers/nvme/host/core.c static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid) · line 3029

Quoted 1 line
drivers/nvme/host/core.c +1 -0
+	timer_setup(&ns->anatt_timer, nvme_anatt_timedout, 0);  

ANATT is per-group, so having a timer in the namespace is wrong.

But would requiring us to create a separate ana group structure on the host. Could be looking into that if you insist, but this implementation is way easier.

For now just add an array of timers, similar to ctrl->ana_state. Eventually we might need an actual ana_group structure if the two arrays become too sparse in practice, but we can defer that for now.

maintainer
#14 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

In reply to Hannes Reinecke
Quoted 48 lines · Hannes Reinecke

Add a timer for tracking ANA transition timeout, and reset the controller once the timeout expires.

Signed-off-by Hannes Reinecke <hare at suse.com>
---
  drivers/nvme/host/core.c      |  3 +++
  drivers/nvme/host/multipath.c | 34 ++++++++++++++++++++++++++++------
  drivers/nvme/host/nvme.h      |  6 ++++++
  3 files changed, 37 insertions(+), 6 deletions(-)
drivers/nvme/host/core.c +3 -0
index e62de51209b2..3b578ae62cb5 100644
@@ -2378,6 +2378,7 @@ int nvme_init_identify(struct nvme_ctrl *ctrl)
  	ctrl->kas = le16_to_cpu(id->kas);
  	ctrl->max_namespaces = le32_to_cpu(id->mnan);
  	ctrl->anacap = id->anacap;
+	ctrl->anatt = id->anatt;
  	ctrl->nanagrpid = le32_to_cpu(id->nanagrpid);
  	ctrl->anagrpmax = le32_to_cpu(id->anagrpmax);
 
@@ -3025,6 +3026,7 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid)
  	blk_queue_flag_set(QUEUE_FLAG_NONROT, ns->queue);
  	ns->queue->queuedata = ns;
  	ns->ctrl = ctrl;
+	timer_setup(&ns->anatt_timer, nvme_anatt_timedout, 0);
 
  	kref_init(&ns->kref);
  	ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */
@@ -3113,6 +3115,7 @@ static void nvme_ns_remove(struct nvme_ns *ns)
  			blk_integrity_unregister(ns->disk);
  	}
 
+	del_timer_sync(&ns->anatt_timer);
  	mutex_lock(&ns->ctrl->subsys->lock);
  	nvme_mpath_clear_current_path(ns);
  	list_del_rcu(&ns->siblings);
drivers/nvme/host/multipath.c +0 -2
index d30229a21c97..7a37f5959ad4 100644
@@ -62,13 +62,12 @@ void nvme_failover_req(struct request *req)
  	 */
  	switch (nvme_req(req)->status & 0x7ff) {
  	case NVME_SC_ANA_TRANSITION:
-		/*
-		 * XXX: We should verify the controller doesn't die on during

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 70

Quoted 10 lines
drivers/nvme/host/multipath.c +5 -4
-		 * the transition.  But that means we per-group timeout from
-		 * when we first hit the change state, so this won't be
-		 * entirely trivial..
-		 */
  		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
+		if (!timer_pending(&ns->anatt_timer)) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}

Shouldn't this del and re-add the timer if its pending?

Quoted 13 lines
drivers/nvme/host/multipath.c +4 -0
  		break;
  	case NVME_SC_ANA_PERSISTENT_LOSS:
  		nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);
@@ -390,11 +389,34 @@ static int nvme_process_ana_log(struct nvme_ctrl *ctrl, bool groups_only)
  static void nvme_ana_work(struct work_struct *work)
  {
  	struct nvme_ctrl *ctrl = container_of(work, struct nvme_ctrl, ana_work);
+	struct nvme_ns *ns;
 
  	nvme_process_ana_log(ctrl, false);
+	down_write(&ctrl->namespaces_rwsem);
+	list_for_each_entry(ns, &ctrl->namespaces, list) {
+		enum nvme_ana_state state = nvme_ns_ana_state(ns);

drivers/nvme/host/multipath.c static int nvme_process_ana_log(struct nvme_ctrl *ctrl, bool groups_only) · line 407

Quoted 10 lines
drivers/nvme/host/multipath.c +10 -0
+
+		if (timer_pending(&ns->anatt_timer) &&
+		    state != NVME_ANA_CHANGE)
+			del_timer_sync(&ns->anatt_timer);
+		if (!timer_pending(&ns->anatt_timer) &&
+		    state == NVME_ANA_CHANGE) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}

Called from couple of sites, can move to helper?

#15 lore raw

PATCH 3/4 nvme: ANA transition timeout handling

Quoted 6 lines · Sagi Grimberg
Quoted 6 lines
drivers/nvme/host/multipath.c +0 -6
-		/*
-		 * XXX: We should verify the controller doesn't die on during
-		 * the transition.  But that means we per-group timeout from
-		 * when we first hit the change state, so this won't be
-		 * entirely trivial..
-		 */
Quoted 8 lines

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 70

Quoted 6 lines
  		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
drivers/nvme/host/multipath.c +5 -0
+		if (!timer_pending(&ns->anatt_timer)) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}

Shouldn't this del and re-add the timer if its pending?

Basically what we need to do is an xchg on the state variable, at which point it can't ever be pending.

#16 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

In reply to Hannes Reinecke

If I/O is terminated due to an invalid ANA state we should be starting the ANATT timer as we really should have received an ANA AEN signalling the state change.

Signed-off-by Hannes Reinecke <hare at suse.com>
---
 drivers/nvme/host/multipath.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
drivers/nvme/host/multipath.c +29 -7
index 7a37f5959ad4..7f34ae260ca9 100644
@@ -49,7 +49,10 @@ static void nvme_update_ana_state(struct nvme_ns *ns, enum nvme_ana_state state)
 void nvme_failover_req(struct request *req)
 {
 	struct nvme_ns *ns = req->q->queuedata;
+	struct device *dev = disk_to_dev(ns->disk);
 	unsigned long flags;
+	enum nvme_ana_state ana_state;
+	bool ana_state_changed = false;
 
 	spin_lock_irqsave(&ns->head->requeue_lock, flags);
 	blk_steal_bios(&ns->head->requeue_list, req);
@@ -60,26 +63,45 @@ void nvme_failover_req(struct request *req)
 	 * Reset the controller for any non-ANA error as we don't know what
 	 * caused the error:
 	 */
+	ana_state = READ_ONCE(ns->ctrl->ana_state[ns->anagrpid]);
 	switch (nvme_req(req)->status & 0x7ff) {
 	case NVME_SC_ANA_TRANSITION:
-		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
-		if (!timer_pending(&ns->anatt_timer)) {
-			ns->anatt_timer.expires =
-				ns->ctrl->anatt * HZ + jiffies;
-			add_timer(&ns->anatt_timer);
+		if (ana_state != NVME_ANA_CHANGE) {
+			nvme_update_ana_state(ns, NVME_ANA_CHANGE);
+			ana_state_changed = true;
 		}
 		break;
 	case NVME_SC_ANA_PERSISTENT_LOSS:
-		nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);
+		if (ana_state != NVME_ANA_PERSISTENT_LOSS) {
+			nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);
+			ana_state_changed = true;
+		}
 		break;
 	case NVME_SC_ANA_INACCESSIBLE:
-		nvme_update_ana_state(ns, NVME_ANA_INACCESSIBLE);
+		if (ana_state != NVME_ANA_INACCESSIBLE) {
+			nvme_update_ana_state(ns, NVME_ANA_INACCESSIBLE);
+			ana_state_changed = true;
+		}
 		break;
 	default:
 		nvme_reset_ctrl(ns->ctrl);
 		break;
 	}
 
+	if (ana_state_changed) {
+		/*
+		 * We should have received an ANA AEN signalling the state
+		 * change. As we haven't (or we wouldn't ever reach here)
+		 * give it a benefit of doubt to start the ANATT timer
+		 * before resetting the controller.
+		 */
+		if (!timer_pending(&ns->anatt_timer)) {
+			ns->anatt_timer.expires =
+				ns->ctrl->anatt * HZ + jiffies;
+			add_timer(&ns->anatt_timer);
+		}
+	}
+
 	kblockd_schedule_work(&ns->head->requeue_work);
 }
 
Signature · 1 line
2.12.3
#17 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 71

Quoted 11 lines
drivers/nvme/host/multipath.c +4 -5
+	ana_state = READ_ONCE(ns->ctrl->ana_state[ns->anagrpid]);
 	switch (nvme_req(req)->status & 0x7ff) {
 	case NVME_SC_ANA_TRANSITION:
-		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
-		if (!timer_pending(&ns->anatt_timer)) {
-			ns->anatt_timer.expires =
-				ns->ctrl->anatt * HZ + jiffies;
-			add_timer(&ns->anatt_timer);
+		if (ana_state != NVME_ANA_CHANGE) {
+			nvme_update_ana_state(ns, NVME_ANA_CHANGE);
+			ana_state_changed = true;

Needs an xchg operation to be atomic.

Quoted 4 lines
drivers/nvme/host/multipath.c +2 -1
 	case NVME_SC_ANA_PERSISTENT_LOSS:
-		nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);
+		if (ana_state != NVME_ANA_PERSISTENT_LOSS) {
+			nvme_update_ana_state(ns, NVME_ANA_PERSISTENT_LOSS);

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 85

Quoted 10 lines
drivers/nvme/host/multipath.c +6 -1
+			ana_state_changed = true;
+		}
 		break;
 	case NVME_SC_ANA_INACCESSIBLE:
-		nvme_update_ana_state(ns, NVME_ANA_INACCESSIBLE);
+		if (ana_state != NVME_ANA_INACCESSIBLE) {
+			nvme_update_ana_state(ns, NVME_ANA_INACCESSIBLE);
+			ana_state_changed = true;
+		}
 		break;

We don't need to start the ANA timer for these states, it should only happen for the CHANGE state.

#18 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 5 lines · On Thu, 7 Jun 2018 14:11:35 +0200 Christoph Hellwig <[email protected]> wrote:
Quoted 5 lines
drivers/nvme/host/multipath.c +1 -2
+	ana_state = READ_ONCE(ns->ctrl->ana_state[ns->anagrpid]);
 	switch (nvme_req(req)->status & 0x7ff) {
 	case NVME_SC_ANA_TRANSITION:
-		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
-		if (!timer_pending(&ns->anatt_timer)) {
Quoted 9 lines

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 71

Quoted 6 lines
drivers/nvme/host/multipath.c +3 -3
-			ns->anatt_timer.expires =
-				ns->ctrl->anatt * HZ + jiffies;
-			add_timer(&ns->anatt_timer);
+		if (ana_state != NVME_ANA_CHANGE) {
+			nvme_update_ana_state(ns, NVME_ANA_CHANGE);
+			ana_state_changed = true;  

Needs an xchg operation to be atomic.

Okay.

Quoted 12 lines
Quoted 12 lines
(quoted diff) +0 -1
 	case NVME_SC_ANA_PERSISTENT_LOSS:
-		nvme_update_ana_state(ns,

NVME_ANA_PERSISTENT_LOSS);

(quoted diff) +2 -0
+		if (ana_state != NVME_ANA_PERSISTENT_LOSS) {
+			nvme_update_ana_state(ns,

NVME_ANA_PERSISTENT_LOSS);

drivers/nvme/host/multipath.c +3 -1
+			ana_state_changed = true;
+		}
 		break;
 	case NVME_SC_ANA_INACCESSIBLE:
-		nvme_update_ana_state(ns, NVME_ANA_INACCESSIBLE);
+		if (ana_state != NVME_ANA_INACCESSIBLE) {
Quoted 9 lines

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 79

Quoted 5 lines
(quoted diff) +1 -0
+			nvme_update_ana_state(ns,

NVME_ANA_INACCESSIBLE);

drivers/nvme/host/multipath.c +2 -0
+			ana_state_changed = true;
+		}
 		break;  

We don't need to start the ANA timer for these states, it should only happen for the CHANGE state.

Hmm. When we're getting these error it means that there is a divergence between the ANA state on the host and the target.

Which really shouldn't happen, as we _should_ have received an ANA AEN signalling us this state. As there is a race condition here (ANA AENs are being send via the admin connection, and it might be that we haven't gotten around to processing the ANA state yet) we should be starting the ANATT timer to catch any outstanding AENs.

And if we're not getting an ANA AEN within ANATT the controller should be considered hosed, and we need to reset it.
Hence the code.

If you disagree, what _should_ be the appropriate handling in this case, ie we have the most current ANA log page, _and_ get this error indicating a state mismatch?

Cheers,

Hannes

#19 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 4 lines · Hannes Reinecke
Quoted 3 lines

We don't need to start the ANA timer for these states, it should only happen for the CHANGE state.

Quoted 8 lines

Hmm. When we're getting these error it means that there is a divergence between the ANA state on the host and the target.

Which really shouldn't happen, as we _should_ have received an ANA AEN signalling us this state. As there is a race condition here (ANA AENs are being send via the admin connection, and it might be that we haven't gotten around to processing the ANA state yet) we should be starting the ANATT timer to catch any outstanding AENs.

We should receive an AEN, yes. But there is absolutely no ordering guarantees either in the ANA spec, or in NVMe in general for multiple queues (admin vs I/O). But that was intentional in the specification, and has nothing to do with ANATT at all.

Quoted 2 lines

And if we're not getting an ANA AEN within ANATT the controller should be considered hosed, and we need to reset it.

I can't find anything in the spec saying that.

Quoted 3 lines

If you disagree, what _should_ be the appropriate handling in this case, ie we have the most current ANA log page, _and_ get this error indicating a state mismatch?

Update our in-memory state ASAP, and then just way for the AEN to happen eventually. As the current code does.

#20 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 14 lines · On Thu, 7 Jun 2018 15:10:26 +0200 Christoph Hellwig <[email protected]> wrote:
Quoted 4 lines · Hannes Reinecke
Quoted 3 lines

We don't need to start the ANA timer for these states, it should only happen for the CHANGE state.

Quoted 8 lines

Hmm. When we're getting these error it means that there is a divergence between the ANA state on the host and the target.

Which really shouldn't happen, as we _should_ have received an ANA AEN signalling us this state. As there is a race condition here (ANA AENs are being send via the admin connection, and it might be that we haven't gotten around to processing the ANA state yet) we should be starting the ANATT timer to catch any outstanding AENs.

Quoted 5 lines

We should receive an AEN, yes. But there is absolutely no ordering guarantees either in the ANA spec, or in NVMe in general for multiple queues (admin vs I/O). But that was intentional in the specification, and has nothing to do with ANATT at all.

Precisely.
Looking closer, there are not even guarantees within which time any target should be sending the AEN.
So we have to start somewhere when defining a sensible timeframe during which we should have received an ANA AEN. And the one timer we already have is ANATT, so it looked logical to use that.

Especially due to this paragraph:

Quoted 5 lines

If no controllers are reporting ANA Optimized state or ANA Non-Optimized state, then a transition may be occurring such that a controller reporting the Inaccessible state may become accessible and the host should retry the command on the controller reporting Inaccessible state for at least ANATT seconds (refer to Figure 109).

which seems to imply to me that we can have an implicit transition on the target while reporting the inaccessible state error, and the use of ANATT here is indeed applicable.

Quoted 5 lines
Quoted 2 lines

And if we're not getting an ANA AEN within ANATT the controller should be considered hosed, and we need to reset it.

I can't find anything in the spec saying that.

No, surely not; any error recovery action are implementation dependent. But the only error recovery we have is controller reset, so ...

Quoted 7 lines
Quoted 3 lines

If you disagree, what _should_ be the appropriate handling in this case, ie we have the most current ANA log page, _and_ get this error indicating a state mismatch?

Update our in-memory state ASAP, and then just way for the AEN to happen eventually. As the current code does.

This code is essentially error handling.
Yes, I do agree that the spec says it shouldn't happen. But this doesn't mean it _can't_ happen (lost frames, anyone?), so we need to prepare for it.

Cheers,

Hannes

#21 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 6 lines · Hannes Reinecke

Precisely.
Looking closer, there are not even guarantees within which time any target should be sending the AEN.
So we have to start somewhere when defining a sensible timeframe during which we should have received an ANA AEN. And the one timer we already have is ANATT, so it looked logical to use that.

ANATT has no relevance here. And more importantly not sending the AEN until some time later is not going to cause us any grave consequence - we already notice the non-live states by the status, so we don't send any I/O.

Quoted 10 lines

Especially due to this paragraph:

Quoted 5 lines

If no controllers are reporting ANA Optimized state or ANA Non-Optimized state, then a transition may be occurring such that a controller reporting the Inaccessible state may become accessible and the host should retry the command on the controller reporting Inaccessible state for at least ANATT seconds (refer to Figure 109).

which seems to imply to me that we can have an implicit transition on the target while reporting the inaccessible state error, and the use of ANATT here is indeed applicable.

But we'll still get the AEN. The whole point is the above is that if we had a queue_if_no_path=0 mode we should not give up before ANATT.

Quoted 1 line

This code is essentially error handling.

Chances are this code is going to add more errors than it handles, as it doesn't follow the spec.

#22 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 1 line · On Thu, 7 Jun 2018 15:46:12 +0200 Christoph Hellwig <[email protected]> wrote:

On Thu, Jun 07, 2018@03:20:36PM +0200, Hannes Reinecke wrote:

[ .. ]

Quoted 9 lines
Quoted 9 lines

Especially due to this paragraph:

Quoted 6 lines

If no controllers are reporting ANA Optimized state or ANA Non-Optimized state, then a transition may be occurring such that a controller reporting the Inaccessible state may become accessible and the host should retry the command on the controller reporting Inaccessible state for at least ANATT seconds (refer to Figure 109).

Quoted 8 lines
Quoted 3 lines

which seems to imply to me that we can have an implicit transition on the target while reporting the inaccessible state error, and the use of ANATT here is indeed applicable.

But we'll still get the AEN. The whole point is the above is that if we had a queue_if_no_path=0 mode we should not give up before ANATT.

There is no guarantee that we will get the AEN.
Yes, the spec says we should, but it's just a single message which might easily be lost if the corresponding frame is dropped. Think of FC, which supposedly is a lossless fabric, yet a sizeable piece of the spec deals with recovery from lost frames. It _will_ happen.

To rephrase my problem statement:

What is the appropriate action if our copy of the ANA log page indicates an optimized or non-optimized ANA state, but we're getting an I/O error back indicating an inaccessible or persistent-loss ANA state? If this is a simple active/passive scenario we will have set all paths to inactive, leaving us with no paths to sent I/O to.

We _could_ try to implement the last-resort handling of sending I/Os to the inaccessible paths (as per ANA base spec), but as we're not having a round-robin selector we're ending up always hitting the first inaccessible path. If that's the 'true' inaccessible one we will never be able to send I/O even though the target is fully operable.

With this patch we detect this situation, and reset the controller forcing us to re-read the ANA log page. After which we can do normal I/O again.

If you have a better solution I'm all ears.

Cheers,

Hannes

#23 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 6 lines · Hannes Reinecke

There is no guarantee that we will get the AEN.
Yes, the spec says we should, but it's just a single message which might easily be lost if the corresponding frame is dropped. Think of FC, which supposedly is a lossless fabric, yet a sizeable piece of the spec deals with recovery from lost frames. It _will_ happen.

NVMe requires a reliable transport. The Fabrics spec very explicitly states that, and PCIe provides the gurantees as well.

If we lose random admin command replies we have much, much deeper problems. E.g. if we lose the AEN reply we would never get any other AEN ever because we are not going to resubmit the command.

maintainer
#24 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

Quoted 8 lines
Quoted 7 lines

There is no guarantee that we will get the AEN.
Yes, the spec says we should, but it's just a single message which might easily be lost if the corresponding frame is dropped. Think of FC, which supposedly is a lossless fabric, yet a sizeable piece of the spec deals with recovery from lost frames. It _will_ happen.

Quoted 6 lines

NVMe requires a reliable transport. The Fabrics spec very explicitly states that, and PCIe provides the gurantees as well.

If we lose random admin command replies we have much, much deeper problems. E.g. if we lose the AEN reply we would never get any other AEN ever because we are not going to resubmit the command.

I agree here, if the wire is AEN is not getting through, the transport is responsible that nothing else makes it through either (reliable, in-order delivery).

maintainer
#25 lore raw

PATCH 4/4 nvme: start ANATT timer on out-of-order state changes

In reply to Hannes Reinecke
Quoted 26 lines · Hannes Reinecke

If I/O is terminated due to an invalid ANA state we should be starting the ANATT timer as we really should have received an ANA AEN signalling the state change.

Signed-off-by Hannes Reinecke <hare at suse.com>
---
  drivers/nvme/host/multipath.c | 36 +++++++++++++++++++++++++++++-------
  1 file changed, 29 insertions(+), 7 deletions(-)
drivers/nvme/host/multipath.c +3 -0
index 7a37f5959ad4..7f34ae260ca9 100644
@@ -49,7 +49,10 @@ static void nvme_update_ana_state(struct nvme_ns *ns, enum nvme_ana_state state)
  void nvme_failover_req(struct request *req)
  {
  	struct nvme_ns *ns = req->q->queuedata;
+	struct device *dev = disk_to_dev(ns->disk);
  	unsigned long flags;
+	enum nvme_ana_state ana_state;
+	bool ana_state_changed = false;
 
  	spin_lock_irqsave(&ns->head->requeue_lock, flags);
  	blk_steal_bios(&ns->head->requeue_list, req);
@@ -60,26 +63,45 @@ void nvme_failover_req(struct request *req)
  	 * Reset the controller for any non-ANA error as we don't know what

drivers/nvme/host/multipath.c void nvme_failover_req(struct request *req) · line 69

Quoted 10 lines
drivers/nvme/host/multipath.c +1 -5
  	 * caused the error:
  	 */
+	ana_state = READ_ONCE(ns->ctrl->ana_state[ns->anagrpid]);
  	switch (nvme_req(req)->status & 0x7ff) {
  	case NVME_SC_ANA_TRANSITION:
-		nvme_update_ana_state(ns, NVME_ANA_CHANGE);
-		if (!timer_pending(&ns->anatt_timer)) {
-			ns->anatt_timer.expires =
-				ns->ctrl->anatt * HZ + jiffies;
-			add_timer(&ns->anatt_timer);

Wasn't this added in the previous patch? its a bit confusing... can you re-arrange the patches differently? maybe squash?