Skip to contentSkip to conversation

nvme: freeze IO accesses around format

PATCH New Jens Axboe 3 participants 13 messages Oct 27, 2017 – Nov 1, 2017 NVMe

lore
Share & tools
lore

Jens Axboe

author maintainer
#0 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

If someone attempts to do IO to a drive while it is under format, we risk timing out that IO. That potentially leads to the driver triggering a controller reset, and subsequently the format is ruined and the device goes away.

Prevents this by freezing IO access to the device around a format. Without this, the following set of commands can easily make your device disappear:

parted -s /dev/nvme3n1 mklabel gpt
parted -s /dev/nvme3n1 mkpart primary 0G 100G
parted -s /dev/nvme3n1 rm 1
nvme format /dev/nvme3

since the last partition removal will trigger a udev partition reload, which happens while the format is running. If the format takes longer than the normal IO timeout, we start timing it out:

[  456.799438]  nvme3n1:
[  456.833656]  nvme3n1: p1
[  456.842025]  nvme3n1: p1
[  456.887368]  nvme3n1:
[  487.699023] nvme nvme3: I/O 879 QID 12 timeout, aborting
[  518.098840] nvme nvme3: I/O 879 QID 12 timeout, reset controller
[  571.700471] nvme nvme3: Abort status: 0x7
[  571.798306] nvme nvme3: Removing after probe failure status: -22
[  571.811330] nvme3n1: detected capacity change from 4000787030016 to 0
[  571.819189] print_req_error: I/O error, dev nvme3n1, sector 7814036992

and the device is gone, needing a driver reload or reboot to bring it back. Same thing happens if you just do a dd from the device and then start a format. Behavior is vendor agnostic, basically just timing dependent.

Signed-off-by Jens Axboe <axboe at kernel.dk>
drivers/nvme/host/core.c +13 -0
index 5a14cc7f28ee..13d7fda73fbc 100644
@@ -1013,10 +1013,23 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 	if (cmd.timeout_ms)
 		timeout = msecs_to_jiffies(cmd.timeout_ms);
 
+	/*
+	 * Freeze current access to the device, and prevent new ones, around
+	 * a format operation.
+	 */
+	if (cmd.opcode == nvme_admin_format_nvm) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);
+	}
+
 	status = nvme_submit_user_cmd(ns ? ns->queue : ctrl->admin_q, &c,
 			(void __user *)(uintptr_t)cmd.addr, cmd.data_len,
 			(void __user *)(uintptr_t)cmd.metadata, cmd.metadata,
 			0, &cmd.result, timeout);
+
+	if (cmd.opcode == nvme_admin_format_nvm)
+		nvme_unfreeze(ctrl);
+
 	if (status >= 0) {
 		if (put_user(cmd.result, &ucmd->result))
 			return -EFAULT;
Signature · 1 line
Jens Axboe
#1 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 29 lines · Jens Axboe

If someone attempts to do IO to a drive while it is under format, we risk timing out that IO. That potentially leads to the driver triggering a controller reset, and subsequently the format is ruined and the device goes away.

Prevents this by freezing IO access to the device around a format. Without this, the following set of commands can easily make your device disappear:

parted -s /dev/nvme3n1 mklabel gpt
parted -s /dev/nvme3n1 mkpart primary 0G 100G
parted -s /dev/nvme3n1 rm 1
nvme format /dev/nvme3

since the last partition removal will trigger a udev partition reload, which happens while the format is running. If the format takes longer than the normal IO timeout, we start timing it out:

[  456.799438]  nvme3n1:
[  456.833656]  nvme3n1: p1
[  456.842025]  nvme3n1: p1
[  456.887368]  nvme3n1:
[  487.699023] nvme nvme3: I/O 879 QID 12 timeout, aborting
[  518.098840] nvme nvme3: I/O 879 QID 12 timeout, reset controller
[  571.700471] nvme nvme3: Abort status: 0x7
[  571.798306] nvme nvme3: Removing after probe failure status: -22
[  571.811330] nvme3n1: detected capacity change from 4000787030016 to 0
[  571.819189] print_req_error: I/O error, dev nvme3n1, sector 7814036992
Quoted 6 lines

and the device is gone, needing a driver reload or reboot to bring it back. Same thing happens if you just do a dd from the device and then start a format. Behavior is vendor agnostic, basically just timing dependent.

Signed-off-by Jens Axboe <axboe at kernel.dk>

Looks good.

Reviewed-by Keith Busch <keith.busch at intel.com>
#2 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

In reply to Jens Axboe
Quoted 11 lines · Jens Axboe
(quoted diff) +7 -0
@@ -1013,10 +1013,23 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 	if (cmd.timeout_ms)
 		timeout = msecs_to_jiffies(cmd.timeout_ms);
 
+	/*
+	 * Freeze current access to the device, and prevent new ones, around
+	 * a format operation.
+	 */
+	if (cmd.opcode == nvme_admin_format_nvm) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);

Review static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns, · line 1032

Quoted 10 lines
(quoted diff) +6 -0
+	}
+
 	status = nvme_submit_user_cmd(ns ? ns->queue : ctrl->admin_q, &c,
 			(void __user *)(uintptr_t)cmd.addr, cmd.data_len,
 			(void __user *)(uintptr_t)cmd.metadata, cmd.metadata,
 			0, &cmd.result, timeout);
+
+	if (cmd.opcode == nvme_admin_format_nvm)
+		nvme_unfreeze(ctrl);
+

Testing some obscure vendor specific IO commands, I noticed this will deadlock if an IO command opcode happens to be the same as an nvme_admin_format_nvm command since the IO queues are all frozen.

You can check 'ns == NULL' before checking the opcode.

I also have this patch that's more generic and will perform actions based on the controller's command effects:

---
commit 760052c4f2d697c19e0f3a78f2f9bc36b2b6b92f

Author: Keith Busch <keith.busch at intel.com>
Date:   Fri Oct 27 10:35:58 2017 -0600

nvme: Check admin passthru command effects

This patch saves the effects log page if the controller supports it, and performs appropriate actions before and after an admin passthrough command is completed.

The NVMe standard provides a command effects log page so the host is aware of actions it may need to do in response to a particular passthrough command. For example, the command may need to run with IO quiesced to prevent timeouts or undefined behavior, or it may change the logical block formats that alter how the host needs to construct commands.

If the controller does not support the command effects log page, the driver will define the effects for known opcodes. The nvme format is the only such command in this patch with known effects.

Signed-off-by Keith Busch <keith.busch at intel.com>
drivers/nvme/host/core.c +113 -9
index 37f9039bb9ca..9b352541672d 100644
@@ -76,6 +76,10 @@ static DEFINE_SPINLOCK(dev_list_lock);
 
 static struct class *nvme_class;
 
+static void nvme_scan(struct nvme_ctrl *ctrl);
+static void nvme_ns_remove(struct nvme_ns *ns);
+static int nvme_revalidate_disk(struct gendisk *disk);
+
 static __le32 nvme_get_log_dw10(u8 lid, size_t size)
 {
 	return cpu_to_le32((((size / 4) - 1) << 16) | lid);
@@ -982,12 +986,68 @@ static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio)
 			metadata, meta_len, io.slba, NULL, 0);
 }
 
+static void nvme_get_admin_effects(struct nvme_ctrl *ctrl, u8 opcode, u32 *effects)
+{
+	if (ctrl->effects)
+		*effects = le32_to_cpu(ctrl->effects->acs[opcode]);
+
+	if (*effects != 0)
+		return;
+
+	/*
+	 * Set known effects for opcodes if the controller doesn't support
+	 * reporting the command's effects.
+	 */
+	switch (opcode) {
+	case nvme_admin_format_nvm:
+		*effects = NVME_CMD_FX_CSUPP | NVME_CMD_FX_LBCC |
+					NVME_CMD_FX_CSE_MASK;
+		break;
+	default:
+		break;
+	}
+}
+
+static void nvme_passthru_start(struct nvme_ctrl *ctrl, u32 effects)
+{
+	if (effects & (NVME_CMD_FX_LBCC | NVME_CMD_FX_CSE_MASK)) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);
+	}
+}
+
+static void nvme_passthru_end(struct nvme_ctrl *ctrl, u32 effects)
+{
+	/*
+	 * Revaildate LBA changes prior to unfreezing. This is necessary to
+	 * prevent memory corruption if a logical block size was changed by
+	 * this command.
+	 */
+	if (effects & NVME_CMD_FX_LBCC) {
+		struct nvme_ns *ns;
+
+		mutex_lock(&ctrl->namespaces_mutex);
+		list_for_each_entry(ns, &ctrl->namespaces, list) {
+			if (ns->disk && nvme_revalidate_disk(ns->disk))
+				nvme_ns_remove(ns);
+		}
+		mutex_unlock(&ctrl->namespaces_mutex);
+	}
+	if (effects & (NVME_CMD_FX_LBCC | NVME_CMD_FX_CSE_MASK))
+		nvme_unfreeze(ctrl);
+	if (effects & NVME_CMD_FX_CCC)
+		nvme_init_identify(ctrl);
+	if (effects & (NVME_CMD_FX_NIC | NVME_CMD_FX_NCC))
+		nvme_scan(ctrl);
+}
+
 static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 			struct nvme_passthru_cmd __user *ucmd)
 {
 	struct nvme_passthru_cmd cmd;
 	struct nvme_command c;
 	unsigned timeout = 0;
+	u32 effects = 0;
 	int status;
 
 	if (!capable(CAP_SYS_ADMIN))
@@ -997,6 +1057,9 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 	if (cmd.flags)
 		return -EINVAL;
 
+	if (ns != NULL)
+		nvme_get_admin_effects(ctrl, cmd.opcode, &effects);
+
 	memset(&c, 0, sizeof(c));
 	c.common.opcode = cmd.opcode;
 	c.common.flags = cmd.flags;
@@ -1013,10 +1076,13 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 	if (cmd.timeout_ms)
 		timeout = msecs_to_jiffies(cmd.timeout_ms);
 
+	nvme_passthru_start(ctrl, effects);
 	status = nvme_submit_user_cmd(ns ? ns->queue : ctrl->admin_q, &c,
 			(void __user *)(uintptr_t)cmd.addr, cmd.data_len,
 			(void __user *)(uintptr_t)cmd.metadata, cmd.metadata,
 			0, &cmd.result, timeout);
+	nvme_passthru_end(ctrl, effects);
+
 	if (status >= 0) {
 		if (put_user(cmd.result, &ucmd->result))
 			return -EFAULT;
@@ -1762,6 +1828,37 @@ static void nvme_init_subnqn(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id)
 	memset(ctrl->subnqn + off, 0, sizeof(ctrl->subnqn) - off);
 }
 
+static int nvme_get_log(struct nvme_ctrl *ctrl, u8 log_page, void *log, size_t size)
+{
+	struct nvme_command c = { };
+
+
+	c.common.opcode = nvme_admin_get_log_page;
+	c.common.nsid = cpu_to_le32(NVME_NSID_ALL);
+	c.common.cdw10[0] = nvme_get_log_dw10(log_page, size);
+
+	return nvme_submit_sync_cmd(ctrl->admin_q, &c, log, size);
+}
+
+static int nvme_get_effects_log(struct nvme_ctrl *ctrl)
+{
+	int ret;
+
+	if (!ctrl->effects)
+		ctrl->effects = kzalloc(sizeof(*ctrl->effects), GFP_KERNEL);
+
+	if (!ctrl->effects)
+		return 0;
+
+	ret = nvme_get_log(ctrl, NVME_LOG_CMD_FX, ctrl->effects,
+					sizeof(*ctrl->effects));
+	if (ret) {
+		kfree(ctrl->effects);
+		ctrl->effects = NULL;
+	}
+	return ret;
+}
+
 /*
  * Initialize the cached copies of the Identify data and various controller
  * register in our nvme_ctrl structure.  This should be called as soon as
@@ -1797,6 +1894,12 @@ int nvme_init_identify(struct nvme_ctrl *ctrl)
 		return -EIO;
 	}
 
+	if (id->lpa & NVME_CTRL_LPA_CMD_FX_LOG) {
+		ret = nvme_get_effects_log(ctrl);
+		if (ret < 0)
+			return ret;
+	}
+
 	nvme_init_subnqn(ctrl, id);
 
 	if (!ctrl->identified) {
@@ -2522,10 +2625,8 @@ static void nvme_scan_ns_sequential(struct nvme_ctrl *ctrl, unsigned nn)
 	nvme_remove_invalid_namespaces(ctrl, nn);
 }
 
-static void nvme_scan_work(struct work_struct *work)
+static void nvme_scan(struct nvme_ctrl *ctrl)
 {
-	struct nvme_ctrl *ctrl =
-		container_of(work, struct nvme_ctrl, scan_work);
 	struct nvme_id_ctrl *id;
 	unsigned nn;
 
@@ -2549,6 +2650,14 @@ static void nvme_scan_work(struct work_struct *work)
 	kfree(id);
 }
 
+static void nvme_scan_work(struct work_struct *work)
+{
+	struct nvme_ctrl *ctrl =
+		container_of(work, struct nvme_ctrl, scan_work);
+
+	nvme_scan(ctrl);
+}
+
 void nvme_queue_scan(struct nvme_ctrl *ctrl)
 {
 	/*
@@ -2615,18 +2724,13 @@ static bool nvme_ctrl_pp_status(struct nvme_ctrl *ctrl)
 
 static void nvme_get_fw_slot_info(struct nvme_ctrl *ctrl)
 {
-	struct nvme_command c = { };
 	struct nvme_fw_slot_info_log *log;
 
 	log = kmalloc(sizeof(*log), GFP_KERNEL);
 	if (!log)
 		return;
 
-	c.common.opcode = nvme_admin_get_log_page;
-	c.common.nsid = cpu_to_le32(NVME_NSID_ALL);
-	c.common.cdw10[0] = nvme_get_log_dw10(NVME_LOG_FW_SLOT, sizeof(*log));
-
-	if (!nvme_submit_sync_cmd(ctrl->admin_q, &c, log, sizeof(*log)))
+	if (nvme_get_log(ctrl, NVME_LOG_FW_SLOT, log, sizeof(*log)))
 		dev_warn(ctrl->device,
 				"Get FW SLOT INFO log error\n");
 	kfree(log);
drivers/nvme/host/nvme.h +1 -0
index d3f3c4447515..69dd8d2cf05c 100644
@@ -172,6 +172,7 @@ struct nvme_ctrl {
 	bool subsystem;
 	unsigned long quirks;
 	struct nvme_id_power_state psd[32];
+	struct nvme_effects_log *effects;
 	struct work_struct scan_work;
 	struct work_struct async_event_work;
 	struct delayed_work ka_work;
include/linux/nvme.h +18 -0
index 9310ce77d8e1..cad7bde44f8e 100644
@@ -267,6 +267,7 @@ enum {
 	NVME_CTRL_OACS_SEC_SUPP                 = 1 << 0,
 	NVME_CTRL_OACS_DIRECTIVES		= 1 << 5,
 	NVME_CTRL_OACS_DBBUF_SUPP		= 1 << 8,
+	NVME_CTRL_LPA_CMD_FX_LOG		= 1 << 1,
 };
 
 struct nvme_lbaf {
@@ -396,6 +397,22 @@ struct nvme_fw_slot_info_log {
 };
 
 enum {
+	NVME_CMD_FX_CSUPP	= 1 << 0,
+	NVME_CMD_FX_LBCC	= 1 << 1,
+	NVME_CMD_FX_NCC		= 1 << 2,
+	NVME_CMD_FX_NIC		= 1 << 3,
+	NVME_CMD_FX_CCC		= 1 << 4,
+	NVME_CMD_FX_CSE_MASK	= 3 << 16,
+};
+
+struct nvme_effects_log {
+	__le32 acs[256];
+	__le32 iocs[256];
+	__u8   resv[2048];
+};
+
+
+enum {
 	NVME_SMART_CRIT_SPARE		= 1 << 0,
 	NVME_SMART_CRIT_TEMPERATURE	= 1 << 1,
 	NVME_SMART_CRIT_RELIABILITY	= 1 << 2,
@@ -712,6 +729,7 @@ enum {
 	NVME_LOG_ERROR		= 0x01,
 	NVME_LOG_SMART		= 0x02,
 	NVME_LOG_FW_SLOT	= 0x03,
+	NVME_LOG_CMD_FX		= 0x05,
 	NVME_LOG_DISC		= 0x70,
 	NVME_LOG_RESERVATION	= 0x80,
 	NVME_FWACT_REPL		= (0 << 3),

--

author maintainer
#3 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 27 lines · Keith Busch
Quoted 11 lines · Jens Axboe
drivers/nvme/host/core.c +7 -0
@@ -1013,10 +1013,23 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns,
 	if (cmd.timeout_ms)
 		timeout = msecs_to_jiffies(cmd.timeout_ms);
 
+	/*
+	 * Freeze current access to the device, and prevent new ones, around
+	 * a format operation.
+	 */
+	if (cmd.opcode == nvme_admin_format_nvm) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);

drivers/nvme/host/core.c static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns, · line 1032

Quoted 10 lines
drivers/nvme/host/core.c +6 -0
+	}
+
 	status = nvme_submit_user_cmd(ns ? ns->queue : ctrl->admin_q, &c,
 			(void __user *)(uintptr_t)cmd.addr, cmd.data_len,
 			(void __user *)(uintptr_t)cmd.metadata, cmd.metadata,
 			0, &cmd.result, timeout);
+
+	if (cmd.opcode == nvme_admin_format_nvm)
+		nvme_unfreeze(ctrl);
+

Testing some obscure vendor specific IO commands, I noticed this will deadlock if an IO command opcode happens to be the same as an nvme_admin_format_nvm command since the IO queues are all frozen.

Quoted 1 line

You can check 'ns == NULL' before checking the opcode.

Yeah good point, it should have checked for this being the admin queue target.

Quoted 2 lines

I also have this patch that's more generic and will perform actions based on the controller's command effects:

Looks fine to me, one minor comment below.

Quoted 11 lines
drivers/nvme/host/core.c +11 -0
+static void nvme_get_admin_effects(struct nvme_ctrl *ctrl, u8 opcode, u32 *effects)
+{
+	if (ctrl->effects)
+		*effects = le32_to_cpu(ctrl->effects->acs[opcode]);
+
+	if (*effects != 0)
+		return;
+
+	/*
+	 * Set known effects for opcodes if the controller doesn't support
+	 * reporting the command's effects.

drivers/nvme/host/core.c static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) · line 1009

Quoted 10 lines
drivers/nvme/host/core.c +10 -0
+	 */
+	switch (opcode) {
+	case nvme_admin_format_nvm:
+		*effects = NVME_CMD_FX_CSUPP | NVME_CMD_FX_LBCC |
+					NVME_CMD_FX_CSE_MASK;
+		break;
+	default:
+		break;
+	}
+}

Returning the value in a passed in pointer is pretty ugly, I only generally think it's acceptable if you have to pass back some error as well. And it's still nasty in that case. How about just returning an u32 from this function?

Signature · 1 line
Jens Axboe
#4 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

In reply to Keith Busch
Quoted 3 lines

If the controller does not support the command effects log page, the driver will define the effects for known opcodes. The nvme format is the only such command in this patch with known effects.

Sanitize is another one. Also Format might either affect a single namespace or the whole controller depending on what it advertises in FNA.

Quoted 1 line

+static void nvme_get_admin_effects(struct nvme_ctrl *ctrl, u8 opcode, u32 *effects)

Overly long line.

Quoted 10 lines
drivers/nvme/host/core.c +10 -0
+{
+	if (ctrl->effects)
+		*effects = le32_to_cpu(ctrl->effects->acs[opcode]);
+
+	if (*effects != 0)
+		return;
+
+	/*
+	 * Set known effects for opcodes if the controller doesn't support
+	 * reporting the command's effects.

drivers/nvme/host/core.c static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) · line 1009

Quoted 10 lines
drivers/nvme/host/core.c +10 -0
+	 */
+	switch (opcode) {
+	case nvme_admin_format_nvm:
+		*effects = NVME_CMD_FX_CSUPP | NVME_CMD_FX_LBCC |
+					NVME_CMD_FX_CSE_MASK;
+		break;
+	default:
+		break;
+	}
+}

Hmm. I'd expect it to be something like:

static u32 nvme_get_admin_effects((struct nvme_ctrl *ctrl, u8 opcode)

{
	if (ctrl->effects)
		return le32_to_cpu(ctrl->effects->acs[opcode]);

	switch (opcode) {
		...
	}
}

That is: a) return the value instead of pass by reference, and
b) if the controller supports the log page rely on it.

Also don't we need to also handle this for I/O commands? While non of the currently defined I/O commands would need anything, the spec defines the mechanism? and it might be useful for vendor specific commands.

drivers/nvme/host/core.c static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) · line 1017

Quoted 7 lines
drivers/nvme/host/core.c +7 -0
+static void nvme_passthru_start(struct nvme_ctrl *ctrl, u32 effects)
+{
+	if (effects & (NVME_CMD_FX_LBCC | NVME_CMD_FX_CSE_MASK)) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);
+	}
+}

I'd move the nvme_get_admin_effects call into this, and return the value from this function to keep the caller a little more uncluttered.

drivers/nvme/host/core.c static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) · line 1035

Quoted 10 lines
drivers/nvme/host/core.c +10 -0
+	if (effects & NVME_CMD_FX_LBCC) {
+		struct nvme_ns *ns;
+
+		mutex_lock(&ctrl->namespaces_mutex);
+		list_for_each_entry(ns, &ctrl->namespaces, list) {
+			if (ns->disk && nvme_revalidate_disk(ns->disk))
+				nvme_ns_remove(ns);
+		}
+		mutex_unlock(&ctrl->namespaces_mutex);
+	}

Split the code above into a helper?

Quoted 1 line

+static int nvme_get_log(struct nvme_ctrl *ctrl, u8 log_page, void *log, size_t size)

To loong line again. Also I think adding this helper should probably be a preparation patch.

drivers/nvme/host/core.c static void nvme_init_subnqn(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) · line 1836

Quoted 5 lines
drivers/nvme/host/core.c +5 -0
+{
+	struct nvme_command c = { };
+
+
+	c.common.opcode = nvme_admin_get_log_page;

Double empty line.

Quoted 8 lines
drivers/nvme/host/core.c +1 -2
+static void nvme_scan(struct nvme_ctrl *ctrl)
 {
-	struct nvme_ctrl *ctrl =
-		container_of(work, struct nvme_ctrl, scan_work);
 	struct nvme_id_ctrl *id;
 	unsigned nn;

drivers/nvme/host/core.c static void nvme_scan_work(struct work_struct *work) · line 2659

Quoted 10 lines
drivers/nvme/host/core.c +7 -0
@@ -2549,6 +2650,14 @@ static void nvme_scan_work(struct work_struct *work)
 	kfree(id);
 }
 
+static void nvme_scan_work(struct work_struct *work)
+{
+	struct nvme_ctrl *ctrl =
+		container_of(work, struct nvme_ctrl, scan_work);
+
+	nvme_scan(ctrl);
+}

Why do we do the scan inline here, but not in any other place?

include/linux/nvme.h enum { · line 270

Quoted 5 lines
include/linux/nvme.h +1 -0
@@ -267,6 +267,7 @@ enum {
 	NVME_CTRL_OACS_SEC_SUPP                 = 1 << 0,
 	NVME_CTRL_OACS_DIRECTIVES		= 1 << 5,
 	NVME_CTRL_OACS_DBBUF_SUPP		= 1 << 8,
+	NVME_CTRL_LPA_CMD_FX_LOG		= 1 << 1,

I'd just use the bit directly, given that it doesn't have a name in the spec either.

include/linux/nvme.h struct nvme_fw_slot_info_log { · line 405

Quoted 7 lines
include/linux/nvme.h +6 -0
 enum {
+	NVME_CMD_FX_CSUPP	= 1 << 0,
+	NVME_CMD_FX_LBCC	= 1 << 1,
+	NVME_CMD_FX_NCC		= 1 << 2,
+	NVME_CMD_FX_NIC		= 1 << 3,
+	NVME_CMD_FX_CCC		= 1 << 4,
+	NVME_CMD_FX_CSE_MASK	= 3 << 16,

s/NVME_CMD_FX/NVME_CMD_EFFECTS/g ?

include/linux/nvme.h enum { · line 732

Quoted 1 line
include/linux/nvme.h +1 -0
+	NVME_LOG_CMD_FX		= 0x05,

same here.

#5 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 7 lines · Christoph Hellwig
Quoted 3 lines

If the controller does not support the command effects log page, the driver will define the effects for known opcodes. The nvme format is the only such command in this patch with known effects.

Sanitize is another one. Also Format might either affect a single namespace or the whole controller depending on what it advertises in FNA.

Okay, we can freeze single nvme namespace's request queues if desired. It's only a little more code to do that.

Quoted 10 lines

static u32 nvme_get_admin_effects((struct nvme_ctrl *ctrl, u8 opcode)

{
	if (ctrl->effects)
		return le32_to_cpu(ctrl->effects->acs[opcode]);

	switch (opcode) {
		...
	}
}
Quoted 2 lines

That is: a) return the value instead of pass by reference, and
b) if the controller supports the log page rely on it.

Agreed, that's better.

Quoted 4 lines

Also don't we need to also handle this for I/O commands? While non of the currently defined I/O commands would need anything, the spec defines the mechanism? and it might be useful for vendor specific commands

That gets tricky. What if the IO command effects says it needs to run exclusively? We don't have a way to quiesce IO queues and then issue our exclusive IO through the frozen queue. We'd deadlock trying to allocate a request from it.

If it's okay, I'd like to handle the IO command effects separately for future work.

Quoted 10 lines

drivers/nvme/host/core.c static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) · line 1017

Quoted 7 lines
drivers/nvme/host/core.c +7 -0
+static void nvme_passthru_start(struct nvme_ctrl *ctrl, u32 effects)
+{
+	if (effects & (NVME_CMD_FX_LBCC | NVME_CMD_FX_CSE_MASK)) {
+		nvme_start_freeze(ctrl);
+		nvme_wait_freeze(ctrl);
+	}
+}

I'd move the nvme_get_admin_effects call into this, and return the value from this function to keep the caller a little more uncluttered.

Sounds good.

Quoted 4 lines
Quoted 1 line

+static int nvme_get_log(struct nvme_ctrl *ctrl, u8 log_page, void *log, size_t size)

To loong line again. Also I think adding this helper should probably be a preparation patch.

Will do.

Quoted 9 lines

drivers/nvme/host/core.c static void nvme_scan_work(struct work_struct *work) · line 2659

Quoted 7 lines
drivers/nvme/host/core.c +7 -0
+static void nvme_scan_work(struct work_struct *work)
+{
+	struct nvme_ctrl *ctrl =
+		container_of(work, struct nvme_ctrl, scan_work);
+
+	nvme_scan(ctrl);
+}

Why do we do the scan inline here, but not in any other place?

Huh, not sure. I wanted the driver to react to the command's effects before returning from the ioctl. I don't know why I thought that was important, though. Will queue it as normal.

Quoted 9 lines

include/linux/nvme.h struct nvme_fw_slot_info_log { · line 405

Quoted 7 lines

enum {

include/linux/nvme.h +6 -0
+	NVME_CMD_FX_CSUPP	= 1 << 0,
+	NVME_CMD_FX_LBCC	= 1 << 1,
+	NVME_CMD_FX_NCC		= 1 << 2,
+	NVME_CMD_FX_NIC		= 1 << 3,
+	NVME_CMD_FX_CCC		= 1 << 4,
+	NVME_CMD_FX_CSE_MASK	= 3 << 16,

s/NVME_CMD_FX/NVME_CMD_EFFECTS/g ?

Just trying to shorten the name with an abbreviation. Will spell it out in the next patch.

Thanks for the feedback.

#6 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 3 lines · Keith Busch

Okay, we can freeze single nvme namespace's request queues if desired. It's only a little more code to do that.

Not sure we need to bother doing it now, but a comment that we are lazy might be useful.

Quoted 5 lines
Quoted 4 lines

Also don't we need to also handle this for I/O commands? While non of the currently defined I/O commands would need anything, the spec defines the mechanism? and it might be useful for vendor specific commands

Quoted 7 lines

That gets tricky. What if the IO command effects says it needs to run exclusively? We don't have a way to quiesce IO queues and then issue our exclusive IO through the frozen queue. We'd deadlock trying to allocate a request from it.

If it's okay, I'd like to handle the IO command effects separately for future work.

True. Maybe we should warn if we have any bit set for I/O commands?

#7 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

In reply to Jens Axboe

And what about sanitize? I guess instead of looking at opcodes directly we'd better look at the indications in the Commands Supported and Effects log. That's what the Windows driver does, btw.

author maintainer
#8 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 3 lines · Christoph Hellwig

And what about sanitize? I guess instead of looking at opcodes directly we'd better look at the indications in the Commands Supported and Effects log. That's what the Windows driver does, btw.

Isn't that exactly what Keith's patch did?

Signature · 1 line
Jens Axboe
#9 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 6 lines · Jens Axboe
Quoted 3 lines · Christoph Hellwig

And what about sanitize? I guess instead of looking at opcodes directly we'd better look at the indications in the Commands Supported and Effects log. That's what the Windows driver does, btw.

Isn't that exactly what Keith's patch did?

Ooops I had missed his patch was a completely different approach.

#10 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 9 lines · Christoph Hellwig
Quoted 6 lines · Jens Axboe
Quoted 3 lines · Christoph Hellwig

And what about sanitize? I guess instead of looking at opcodes directly we'd better look at the indications in the Commands Supported and Effects log. That's what the Windows driver does, btw.

Isn't that exactly what Keith's patch did?

Ooops I had missed his patch was a completely different approach.

I'll send it out in a new thread. I have fix the command effects log page memory leak, anyway.

#11 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

In reply to Jens Axboe
Quoted 8 lines · Jens Axboe

Prevents this by freezing IO access to the device around a format. Without this, the following set of commands can easily make your device disappear:

parted -s /dev/nvme3n1 mklabel gpt
parted -s /dev/nvme3n1 mkpart primary 0G 100G
parted -s /dev/nvme3n1 rm 1
nvme format /dev/nvme3

Can you wire this test case up for blktests?

author maintainer
#12 lore raw

PATCH 1/1 nvme: freeze IO accesses around format

Quoted 11 lines · Christoph Hellwig
Quoted 8 lines · Jens Axboe

Prevents this by freezing IO access to the device around a format. Without this, the following set of commands can easily make your device disappear:

parted -s /dev/nvme3n1 mklabel gpt
parted -s /dev/nvme3n1 mkpart primary 0G 100G
parted -s /dev/nvme3n1 rm 1
nvme format /dev/nvme3

Can you wire this test case up for blktests?

Yeah that's a good idea, I'll do that.

Signature · 1 line
Jens Axboe