diff mbox

[1/2] libnvdimm/nfit_test: add firmware download emulation

Message ID 151698930627.35603.14847307775995809534.stgit@djiang5-desk3.ch.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dave Jiang Jan. 26, 2018, 5:55 p.m. UTC
Adding support in nfit_test for DSM v1.6 firmware update sequence. The test
will simulate the flashing of firmware to the DIMM. A bogus version string
will be returned as the test has no idea how to parse the firmware binary.
Any bogus binary can be used to "update" as the actual binary is not copied
into the kernel.

Signed-off-by: Dave Jiang <dave.jiang@intel.com>
---
 tools/testing/nvdimm/test/nfit.c      |  293 ++++++++++++++++++++++++++++++++-
 tools/testing/nvdimm/test/nfit_test.h |   66 +++++++
 2 files changed, 345 insertions(+), 14 deletions(-)

Comments

Verma, Vishal L Jan. 31, 2018, 9:32 p.m. UTC | #1
On Fri, 2018-01-26 at 10:55 -0700, Dave Jiang wrote:
> Adding support in nfit_test for DSM v1.6 firmware update sequence.
> The test
> will simulate the flashing of firmware to the DIMM. A bogus version
> string
> will be returned as the test has no idea how to parse the firmware
> binary.
> Any bogus binary can be used to "update" as the actual binary is not
> copied
> into the kernel.
> 
> Signed-off-by: Dave Jiang <dave.jiang@intel.com>
> ---
>  tools/testing/nvdimm/test/nfit.c      |  293
> ++++++++++++++++++++++++++++++++-
>  tools/testing/nvdimm/test/nfit_test.h |   66 +++++++
>  2 files changed, 345 insertions(+), 14 deletions(-)
> 

For both of these,
Reviewed-by: Vishal Verma <vishal.l.verma@intel.com>

> diff --git a/tools/testing/nvdimm/test/nfit.c
> b/tools/testing/nvdimm/test/nfit.c
> index 2b57254342aa..3d999f193471 100644
> --- a/tools/testing/nvdimm/test/nfit.c
> +++ b/tools/testing/nvdimm/test/nfit.c
> @@ -137,6 +137,14 @@ static u32 handle[] = {
>  
>  static unsigned long dimm_fail_cmd_flags[NUM_DCR];
>  
> +struct nfit_test_fw {
> +	enum intel_fw_update_state state;
> +	u32 context;
> +	u64 version;
> +	u32 size_received;
> +	u64 end_time;
> +};
> +
>  struct nfit_test {
>  	struct acpi_nfit_desc acpi_desc;
>  	struct platform_device pdev;
> @@ -172,6 +180,7 @@ struct nfit_test {
>  	struct nd_intel_smart_threshold *smart_threshold;
>  	struct badrange badrange;
>  	struct work_struct work;
> +	struct nfit_test_fw *fw;
>  };
>  
>  static struct workqueue_struct *nfit_wq;
> @@ -183,6 +192,226 @@ static struct nfit_test *to_nfit_test(struct
> device *dev)
>  	return container_of(pdev, struct nfit_test, pdev);
>  }
>  
> +static int nd_intel_test_get_fw_info(struct nfit_test *t,
> +		struct nd_intel_fw_info *nd_cmd, unsigned int
> buf_len,
> +		int idx)
> +{
> +	struct device *dev = &t->pdev.dev;
> +	struct nfit_test_fw *fw = &t->fw[idx];
> +
> +	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p, buf_len: %u, idx:
> %d\n",
> +			__func__, t, nd_cmd, buf_len, idx);
> +
> +	if (buf_len < sizeof(*nd_cmd))
> +		return -EINVAL;
> +
> +	nd_cmd->status = 0;
> +	nd_cmd->storage_size = INTEL_FW_STORAGE_SIZE;
> +	nd_cmd->max_send_len = INTEL_FW_MAX_SEND_LEN;
> +	nd_cmd->query_interval = INTEL_FW_QUERY_INTERVAL;
> +	nd_cmd->max_query_time = INTEL_FW_QUERY_MAX_TIME;
> +	nd_cmd->update_cap = 0;
> +	nd_cmd->fis_version = INTEL_FW_FIS_VERSION;
> +	nd_cmd->run_version = 0;
> +	nd_cmd->updated_version = fw->version;
> +
> +	return 0;
> +}
> +
> +static int nd_intel_test_start_update(struct nfit_test *t,
> +		struct nd_intel_fw_start *nd_cmd, unsigned int
> buf_len,
> +		int idx)
> +{
> +	struct device *dev = &t->pdev.dev;
> +	struct nfit_test_fw *fw = &t->fw[idx];
> +
> +	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx:
> %d)\n",
> +			__func__, t, nd_cmd, buf_len, idx);
> +
> +	if (buf_len < sizeof(*nd_cmd))
> +		return -EINVAL;
> +
> +	if (fw->state != FW_STATE_NEW) {
> +		/* extended status, FW update in progress */
> +		nd_cmd->status = 0x10007;
> +		return 0;
> +	}
> +
> +	fw->state = FW_STATE_IN_PROGRESS;
> +	fw->context++;
> +	fw->size_received = 0;
> +	nd_cmd->status = 0;
> +	nd_cmd->context = fw->context;
> +
> +	dev_dbg(dev, "%s: context issued: %#x\n", __func__, nd_cmd-
> >context);
> +
> +	return 0;
> +}
> +
> +static int nd_intel_test_send_data(struct nfit_test *t,
> +		struct nd_intel_fw_send_data *nd_cmd, unsigned int
> buf_len,
> +		int idx)
> +{
> +	struct device *dev = &t->pdev.dev;
> +	struct nfit_test_fw *fw = &t->fw[idx];
> +	u32 *status = (u32 *)&nd_cmd->data[nd_cmd->length];
> +
> +	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx:
> %d)\n",
> +			__func__, t, nd_cmd, buf_len, idx);
> +
> +	if (buf_len < sizeof(*nd_cmd))
> +		return -EINVAL;
> +
> +
> +	dev_dbg(dev, "%s: cmd->status: %#x\n", __func__, *status);
> +	dev_dbg(dev, "%s: cmd->data[0]: %#x\n", __func__, nd_cmd-
> >data[0]);
> +	dev_dbg(dev, "%s: cmd->data[%u]: %#x\n", __func__, nd_cmd-
> >length-1,
> +			nd_cmd->data[nd_cmd->length-1]);
> +
> +	if (fw->state != FW_STATE_IN_PROGRESS) {
> +		dev_dbg(dev, "%s: not in IN_PROGRESS state\n",
> __func__);
> +		*status = 0x5;
> +		return 0;
> +	}
> +
> +	if (nd_cmd->context != fw->context) {
> +		dev_dbg(dev, "%s: incorrect context: in: %#x
> correct: %#x\n",
> +				__func__, nd_cmd->context, fw-
> >context);
> +		*status = 0x10007;
> +		return 0;
> +	}
> +
> +	/*
> +	 * check offset + len > size of fw storage
> +	 * check length is > max send length
> +	 */
> +	if (nd_cmd->offset + nd_cmd->length > INTEL_FW_STORAGE_SIZE
> ||
> +			nd_cmd->length > INTEL_FW_MAX_SEND_LEN) {
> +		*status = 0x3;
> +		dev_dbg(dev, "%s: buffer boundary violation\n",
> __func__);
> +		return 0;
> +	}
> +
> +	fw->size_received += nd_cmd->length;
> +	dev_dbg(dev, "%s: copying %u bytes, %u bytes so far\n",
> +			__func__, nd_cmd->length, fw-
> >size_received);
> +	*status = 0;
> +	return 0;
> +}
> +
> +static int nd_intel_test_finish_fw(struct nfit_test *t,
> +		struct nd_intel_fw_finish_update *nd_cmd,
> +		unsigned int buf_len, int idx)
> +{
> +	struct device *dev = &t->pdev.dev;
> +	struct nfit_test_fw *fw = &t->fw[idx];
> +
> +	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx:
> %d)\n",
> +			__func__, t, nd_cmd, buf_len, idx);
> +
> +	if (fw->state == FW_STATE_UPDATED) {
> +		/* update already done, need cold boot */
> +		nd_cmd->status = 0x20007;
> +		return 0;
> +	}
> +
> +	dev_dbg(dev, "%s: context: %#x  ctrl_flags: %#x\n",
> +			__func__, nd_cmd->context, nd_cmd-
> >ctrl_flags);
> +
> +	switch (nd_cmd->ctrl_flags) {
> +	case 0: /* finish */
> +		if (nd_cmd->context != fw->context) {
> +			dev_dbg(dev, "%s: incorrect context: in: %#x
> correct: %#x\n",
> +					__func__, nd_cmd->context,
> +					fw->context);
> +			nd_cmd->status = 0x10007;
> +			return 0;
> +		}
> +		nd_cmd->status = 0;
> +		fw->state = FW_STATE_VERIFY;
> +		/* set 1 second of time for firmware "update" */
> +		fw->end_time = jiffies + HZ;
> +		break;
> +
> +	case 1: /* abort */
> +		fw->size_received = 0;
> +		/* successfully aborted status */
> +		nd_cmd->status = 0x40007;
> +		fw->state = FW_STATE_NEW;
> +		dev_dbg(dev, "%s: abort successful\n", __func__);
> +		break;
> +
> +	default: /* bad control flag */
> +		dev_warn(dev, "%s: unknown control flag: %#x\n",
> +				__func__, nd_cmd->ctrl_flags);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int nd_intel_test_finish_query(struct nfit_test *t,
> +		struct nd_intel_fw_finish_query *nd_cmd,
> +		unsigned int buf_len, int idx)
> +{
> +	struct device *dev = &t->pdev.dev;
> +	struct nfit_test_fw *fw = &t->fw[idx];
> +
> +	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx:
> %d)\n",
> +			__func__, t, nd_cmd, buf_len, idx);
> +
> +	if (buf_len < sizeof(*nd_cmd))
> +		return -EINVAL;
> +
> +	if (nd_cmd->context != fw->context) {
> +		dev_dbg(dev, "%s: incorrect context: in: %#x
> correct: %#x\n",
> +				__func__, nd_cmd->context, fw-
> >context);
> +		nd_cmd->status = 0x10007;
> +		return 0;
> +	}
> +
> +	dev_dbg(dev, "%s context: %#x\n", __func__, nd_cmd-
> >context);
> +
> +	switch (fw->state) {
> +	case FW_STATE_NEW:
> +		nd_cmd->updated_fw_rev = 0;
> +		nd_cmd->status = 0;
> +		dev_dbg(dev, "%s: new state\n", __func__);
> +		break;
> +
> +	case FW_STATE_IN_PROGRESS:
> +		/* sequencing error */
> +		nd_cmd->status = 0x40007;
> +		nd_cmd->updated_fw_rev = 0;
> +		dev_dbg(dev, "%s: sequence error\n", __func__);
> +		break;
> +
> +	case FW_STATE_VERIFY:
> +		if (time_is_after_jiffies64(fw->end_time)) {
> +			nd_cmd->updated_fw_rev = 0;
> +			nd_cmd->status = 0x20007;
> +			dev_dbg(dev, "%s: still verifying\n",
> __func__);
> +			break;
> +		}
> +
> +		dev_dbg(dev, "%s: transition out verify\n",
> __func__);
> +		fw->state = FW_STATE_UPDATED;
> +		/* we are going to fall through if it's "done" */
> +	case FW_STATE_UPDATED:
> +		nd_cmd->status = 0;
> +		/* bogus test version */
> +		fw->version = nd_cmd->updated_fw_rev =
> +			INTEL_FW_FAKE_VERSION;
> +		dev_dbg(dev, "%s: updated\n", __func__);
> +		break;
> +
> +	default: /* we should never get here */
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>  static int nfit_test_cmd_get_config_size(struct
> nd_cmd_get_config_size *nd_cmd,
>  		unsigned int buf_len)
>  {
> @@ -592,6 +821,23 @@ static int
> nfit_test_cmd_ars_inject_status(struct nfit_test *t,
>  	return 0;
>  }
>  
> +static int get_dimm(struct nfit_mem *nfit_mem, unsigned int func)
> +{
> +	int i;
> +
> +	/* lookup per-dimm data */
> +	for (i = 0; i < ARRAY_SIZE(handle); i++)
> +		if (__to_nfit_memdev(nfit_mem)->device_handle ==
> handle[i])
> +			break;
> +	if (i >= ARRAY_SIZE(handle))
> +		return -ENXIO;
> +
> +	if ((1 << func) & dimm_fail_cmd_flags[i])
> +		return -EIO;
> +
> +	return i;
> +}
> +
>  static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc,
>  		struct nvdimm *nvdimm, unsigned int cmd, void *buf,
>  		unsigned int buf_len, int *cmd_rc)
> @@ -620,22 +866,39 @@ static int nfit_test_ctl(struct
> nvdimm_bus_descriptor *nd_desc,
>  			func = call_pkg->nd_command;
>  			if (call_pkg->nd_family != nfit_mem->family)
>  				return -ENOTTY;
> +
> +			i = get_dimm(nfit_mem, func);
> +			if (i < 0)
> +				return i;
> +
> +			switch (func) {
> +			case ND_INTEL_FW_GET_INFO:
> +				return nd_intel_test_get_fw_info(t,
> buf,
> +						buf_len, i - t-
> >dcr_idx);
> +			case ND_INTEL_FW_START_UPDATE:
> +				return nd_intel_test_start_update(t,
> buf,
> +						buf_len, i - t-
> >dcr_idx);
> +			case ND_INTEL_FW_SEND_DATA:
> +				return nd_intel_test_send_data(t,
> buf,
> +						buf_len, i - t-
> >dcr_idx);
> +			case ND_INTEL_FW_FINISH_UPDATE:
> +				return nd_intel_test_finish_fw(t,
> buf,
> +						buf_len, i - t-
> >dcr_idx);
> +			case ND_INTEL_FW_FINISH_QUERY:
> +				return nd_intel_test_finish_query(t,
> buf,
> +						buf_len, i - t-
> >dcr_idx);
> +			default:
> +				return -ENOTTY;
> +			}
>  		}
>  
>  		if (!test_bit(cmd, &cmd_mask)
>  				|| !test_bit(func, &nfit_mem-
> >dsm_mask))
>  			return -ENOTTY;
>  
> -		/* lookup per-dimm data */
> -		for (i = 0; i < ARRAY_SIZE(handle); i++)
> -			if (__to_nfit_memdev(nfit_mem)-
> >device_handle ==
> -					handle[i])
> -				break;
> -		if (i >= ARRAY_SIZE(handle))
> -			return -ENXIO;
> -
> -		if ((1 << func) & dimm_fail_cmd_flags[i])
> -			return -EIO;
> +		i = get_dimm(nfit_mem, func);
> +		if (i < 0)
> +			return i;
>  
>  		switch (func) {
>  		case ND_CMD_GET_CONFIG_SIZE:
> @@ -1728,6 +1991,11 @@ static void nfit_test0_setup(struct nfit_test
> *t)
>  	set_bit(NFIT_CMD_ARS_INJECT_SET, &acpi_desc-
> >bus_nfit_cmd_force_en);
>  	set_bit(NFIT_CMD_ARS_INJECT_CLEAR, &acpi_desc-
> >bus_nfit_cmd_force_en);
>  	set_bit(NFIT_CMD_ARS_INJECT_GET, &acpi_desc-
> >bus_nfit_cmd_force_en);
> +	set_bit(ND_INTEL_FW_GET_INFO, &acpi_desc-
> >dimm_cmd_force_en);
> +	set_bit(ND_INTEL_FW_START_UPDATE, &acpi_desc-
> >dimm_cmd_force_en);
> +	set_bit(ND_INTEL_FW_SEND_DATA, &acpi_desc-
> >dimm_cmd_force_en);
> +	set_bit(ND_INTEL_FW_FINISH_UPDATE, &acpi_desc-
> >dimm_cmd_force_en);
> +	set_bit(ND_INTEL_FW_FINISH_QUERY, &acpi_desc-
> >dimm_cmd_force_en);
>  }
>  
>  static void nfit_test1_setup(struct nfit_test *t)
> @@ -2134,10 +2402,13 @@ static int nfit_test_probe(struct
> platform_device *pdev)
>  		nfit_test->smart_threshold = devm_kcalloc(dev, num,
>  				sizeof(struct
> nd_intel_smart_threshold),
>  				GFP_KERNEL);
> +		nfit_test->fw = devm_kcalloc(dev, num,
> +				sizeof(struct nfit_test_fw),
> GFP_KERNEL);
>  		if (nfit_test->dimm && nfit_test->dimm_dma &&
> nfit_test->label
>  				&& nfit_test->label_dma &&
> nfit_test->dcr
>  				&& nfit_test->dcr_dma && nfit_test-
> >flush
> -				&& nfit_test->flush_dma)
> +				&& nfit_test->flush_dma
> +				&& nfit_test->fw)
>  			/* pass */;
>  		else
>  			return -ENOMEM;
> diff --git a/tools/testing/nvdimm/test/nfit_test.h
> b/tools/testing/nvdimm/test/nfit_test.h
> index ba230f6f7676..be8fa8ec0615 100644
> --- a/tools/testing/nvdimm/test/nfit_test.h
> +++ b/tools/testing/nvdimm/test/nfit_test.h
> @@ -84,9 +84,14 @@ struct nd_cmd_ars_err_inj_stat {
>  	} __packed record[0];
>  } __packed;
>  
> -#define ND_INTEL_SMART 1
> -#define ND_INTEL_SMART_THRESHOLD 2
> -#define ND_INTEL_SMART_SET_THRESHOLD 17
> +#define ND_INTEL_SMART			 1
> +#define ND_INTEL_SMART_THRESHOLD	 2
> +#define ND_INTEL_FW_GET_INFO		12
> +#define ND_INTEL_FW_START_UPDATE	13
> +#define ND_INTEL_FW_SEND_DATA		14
> +#define ND_INTEL_FW_FINISH_UPDATE	15
> +#define ND_INTEL_FW_FINISH_QUERY	16
> +#define ND_INTEL_SMART_SET_THRESHOLD	17
>  
>  #define ND_INTEL_SMART_HEALTH_VALID             (1 << 0)
>  #define ND_INTEL_SMART_SPARES_VALID             (1 << 1)
> @@ -152,6 +157,61 @@ struct nd_intel_smart_set_threshold {
>  	__u32 status;
>  } __packed;
>  
> +#define INTEL_FW_STORAGE_SIZE		0x100000
> +#define INTEL_FW_MAX_SEND_LEN		0xFFEC
> +#define INTEL_FW_QUERY_INTERVAL		250000
> +#define INTEL_FW_QUERY_MAX_TIME		3000000
> +#define INTEL_FW_FIS_VERSION		0x0105
> +#define INTEL_FW_FAKE_VERSION		0xffffffffabcd
> +
> +enum intel_fw_update_state {
> +	FW_STATE_NEW = 0,
> +	FW_STATE_IN_PROGRESS,
> +	FW_STATE_VERIFY,
> +	FW_STATE_UPDATED,
> +};
> +
> +struct nd_intel_fw_info {
> +	__u32 status;
> +	__u32 storage_size;
> +	__u32 max_send_len;
> +	__u32 query_interval;
> +	__u32 max_query_time;
> +	__u8 update_cap;
> +	__u8 reserved[3];
> +	__u32 fis_version;
> +	__u64 run_version;
> +	__u64 updated_version;
> +} __packed;
> +
> +struct nd_intel_fw_start {
> +	__u32 status;
> +	__u32 context;
> +} __packed;
> +
> +/* this one has the output first because the variable input data
> size */
> +struct nd_intel_fw_send_data {
> +	__u32 context;
> +	__u32 offset;
> +	__u32 length;
> +	__u8 data[0];
> +/* this field is not declared due ot variable data from input */
> +/*	__u32 status; */
> +} __packed;
> +
> +struct nd_intel_fw_finish_update {
> +	__u8 ctrl_flags;
> +	__u8 reserved[3];
> +	__u32 context;
> +	__u32 status;
> +} __packed;
> +
> +struct nd_intel_fw_finish_query {
> +	__u32 context;
> +	__u32 status;
> +	__u64 updated_fw_rev;
> +} __packed;
> +
>  union acpi_object;
>  typedef void *acpi_handle;
>  
> 
> _______________________________________________
> Linux-nvdimm mailing list
> Linux-nvdimm@lists.01.org
> https://lists.01.org/mailman/listinfo/linux-nvdimm
diff mbox

Patch

diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c
index 2b57254342aa..3d999f193471 100644
--- a/tools/testing/nvdimm/test/nfit.c
+++ b/tools/testing/nvdimm/test/nfit.c
@@ -137,6 +137,14 @@  static u32 handle[] = {
 
 static unsigned long dimm_fail_cmd_flags[NUM_DCR];
 
+struct nfit_test_fw {
+	enum intel_fw_update_state state;
+	u32 context;
+	u64 version;
+	u32 size_received;
+	u64 end_time;
+};
+
 struct nfit_test {
 	struct acpi_nfit_desc acpi_desc;
 	struct platform_device pdev;
@@ -172,6 +180,7 @@  struct nfit_test {
 	struct nd_intel_smart_threshold *smart_threshold;
 	struct badrange badrange;
 	struct work_struct work;
+	struct nfit_test_fw *fw;
 };
 
 static struct workqueue_struct *nfit_wq;
@@ -183,6 +192,226 @@  static struct nfit_test *to_nfit_test(struct device *dev)
 	return container_of(pdev, struct nfit_test, pdev);
 }
 
+static int nd_intel_test_get_fw_info(struct nfit_test *t,
+		struct nd_intel_fw_info *nd_cmd, unsigned int buf_len,
+		int idx)
+{
+	struct device *dev = &t->pdev.dev;
+	struct nfit_test_fw *fw = &t->fw[idx];
+
+	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p, buf_len: %u, idx: %d\n",
+			__func__, t, nd_cmd, buf_len, idx);
+
+	if (buf_len < sizeof(*nd_cmd))
+		return -EINVAL;
+
+	nd_cmd->status = 0;
+	nd_cmd->storage_size = INTEL_FW_STORAGE_SIZE;
+	nd_cmd->max_send_len = INTEL_FW_MAX_SEND_LEN;
+	nd_cmd->query_interval = INTEL_FW_QUERY_INTERVAL;
+	nd_cmd->max_query_time = INTEL_FW_QUERY_MAX_TIME;
+	nd_cmd->update_cap = 0;
+	nd_cmd->fis_version = INTEL_FW_FIS_VERSION;
+	nd_cmd->run_version = 0;
+	nd_cmd->updated_version = fw->version;
+
+	return 0;
+}
+
+static int nd_intel_test_start_update(struct nfit_test *t,
+		struct nd_intel_fw_start *nd_cmd, unsigned int buf_len,
+		int idx)
+{
+	struct device *dev = &t->pdev.dev;
+	struct nfit_test_fw *fw = &t->fw[idx];
+
+	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx: %d)\n",
+			__func__, t, nd_cmd, buf_len, idx);
+
+	if (buf_len < sizeof(*nd_cmd))
+		return -EINVAL;
+
+	if (fw->state != FW_STATE_NEW) {
+		/* extended status, FW update in progress */
+		nd_cmd->status = 0x10007;
+		return 0;
+	}
+
+	fw->state = FW_STATE_IN_PROGRESS;
+	fw->context++;
+	fw->size_received = 0;
+	nd_cmd->status = 0;
+	nd_cmd->context = fw->context;
+
+	dev_dbg(dev, "%s: context issued: %#x\n", __func__, nd_cmd->context);
+
+	return 0;
+}
+
+static int nd_intel_test_send_data(struct nfit_test *t,
+		struct nd_intel_fw_send_data *nd_cmd, unsigned int buf_len,
+		int idx)
+{
+	struct device *dev = &t->pdev.dev;
+	struct nfit_test_fw *fw = &t->fw[idx];
+	u32 *status = (u32 *)&nd_cmd->data[nd_cmd->length];
+
+	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx: %d)\n",
+			__func__, t, nd_cmd, buf_len, idx);
+
+	if (buf_len < sizeof(*nd_cmd))
+		return -EINVAL;
+
+
+	dev_dbg(dev, "%s: cmd->status: %#x\n", __func__, *status);
+	dev_dbg(dev, "%s: cmd->data[0]: %#x\n", __func__, nd_cmd->data[0]);
+	dev_dbg(dev, "%s: cmd->data[%u]: %#x\n", __func__, nd_cmd->length-1,
+			nd_cmd->data[nd_cmd->length-1]);
+
+	if (fw->state != FW_STATE_IN_PROGRESS) {
+		dev_dbg(dev, "%s: not in IN_PROGRESS state\n", __func__);
+		*status = 0x5;
+		return 0;
+	}
+
+	if (nd_cmd->context != fw->context) {
+		dev_dbg(dev, "%s: incorrect context: in: %#x correct: %#x\n",
+				__func__, nd_cmd->context, fw->context);
+		*status = 0x10007;
+		return 0;
+	}
+
+	/*
+	 * check offset + len > size of fw storage
+	 * check length is > max send length
+	 */
+	if (nd_cmd->offset + nd_cmd->length > INTEL_FW_STORAGE_SIZE ||
+			nd_cmd->length > INTEL_FW_MAX_SEND_LEN) {
+		*status = 0x3;
+		dev_dbg(dev, "%s: buffer boundary violation\n", __func__);
+		return 0;
+	}
+
+	fw->size_received += nd_cmd->length;
+	dev_dbg(dev, "%s: copying %u bytes, %u bytes so far\n",
+			__func__, nd_cmd->length, fw->size_received);
+	*status = 0;
+	return 0;
+}
+
+static int nd_intel_test_finish_fw(struct nfit_test *t,
+		struct nd_intel_fw_finish_update *nd_cmd,
+		unsigned int buf_len, int idx)
+{
+	struct device *dev = &t->pdev.dev;
+	struct nfit_test_fw *fw = &t->fw[idx];
+
+	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx: %d)\n",
+			__func__, t, nd_cmd, buf_len, idx);
+
+	if (fw->state == FW_STATE_UPDATED) {
+		/* update already done, need cold boot */
+		nd_cmd->status = 0x20007;
+		return 0;
+	}
+
+	dev_dbg(dev, "%s: context: %#x  ctrl_flags: %#x\n",
+			__func__, nd_cmd->context, nd_cmd->ctrl_flags);
+
+	switch (nd_cmd->ctrl_flags) {
+	case 0: /* finish */
+		if (nd_cmd->context != fw->context) {
+			dev_dbg(dev, "%s: incorrect context: in: %#x correct: %#x\n",
+					__func__, nd_cmd->context,
+					fw->context);
+			nd_cmd->status = 0x10007;
+			return 0;
+		}
+		nd_cmd->status = 0;
+		fw->state = FW_STATE_VERIFY;
+		/* set 1 second of time for firmware "update" */
+		fw->end_time = jiffies + HZ;
+		break;
+
+	case 1: /* abort */
+		fw->size_received = 0;
+		/* successfully aborted status */
+		nd_cmd->status = 0x40007;
+		fw->state = FW_STATE_NEW;
+		dev_dbg(dev, "%s: abort successful\n", __func__);
+		break;
+
+	default: /* bad control flag */
+		dev_warn(dev, "%s: unknown control flag: %#x\n",
+				__func__, nd_cmd->ctrl_flags);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int nd_intel_test_finish_query(struct nfit_test *t,
+		struct nd_intel_fw_finish_query *nd_cmd,
+		unsigned int buf_len, int idx)
+{
+	struct device *dev = &t->pdev.dev;
+	struct nfit_test_fw *fw = &t->fw[idx];
+
+	dev_dbg(dev, "%s(nfit_test: %p nd_cmd: %p buf_len: %u idx: %d)\n",
+			__func__, t, nd_cmd, buf_len, idx);
+
+	if (buf_len < sizeof(*nd_cmd))
+		return -EINVAL;
+
+	if (nd_cmd->context != fw->context) {
+		dev_dbg(dev, "%s: incorrect context: in: %#x correct: %#x\n",
+				__func__, nd_cmd->context, fw->context);
+		nd_cmd->status = 0x10007;
+		return 0;
+	}
+
+	dev_dbg(dev, "%s context: %#x\n", __func__, nd_cmd->context);
+
+	switch (fw->state) {
+	case FW_STATE_NEW:
+		nd_cmd->updated_fw_rev = 0;
+		nd_cmd->status = 0;
+		dev_dbg(dev, "%s: new state\n", __func__);
+		break;
+
+	case FW_STATE_IN_PROGRESS:
+		/* sequencing error */
+		nd_cmd->status = 0x40007;
+		nd_cmd->updated_fw_rev = 0;
+		dev_dbg(dev, "%s: sequence error\n", __func__);
+		break;
+
+	case FW_STATE_VERIFY:
+		if (time_is_after_jiffies64(fw->end_time)) {
+			nd_cmd->updated_fw_rev = 0;
+			nd_cmd->status = 0x20007;
+			dev_dbg(dev, "%s: still verifying\n", __func__);
+			break;
+		}
+
+		dev_dbg(dev, "%s: transition out verify\n", __func__);
+		fw->state = FW_STATE_UPDATED;
+		/* we are going to fall through if it's "done" */
+	case FW_STATE_UPDATED:
+		nd_cmd->status = 0;
+		/* bogus test version */
+		fw->version = nd_cmd->updated_fw_rev =
+			INTEL_FW_FAKE_VERSION;
+		dev_dbg(dev, "%s: updated\n", __func__);
+		break;
+
+	default: /* we should never get here */
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int nfit_test_cmd_get_config_size(struct nd_cmd_get_config_size *nd_cmd,
 		unsigned int buf_len)
 {
@@ -592,6 +821,23 @@  static int nfit_test_cmd_ars_inject_status(struct nfit_test *t,
 	return 0;
 }
 
+static int get_dimm(struct nfit_mem *nfit_mem, unsigned int func)
+{
+	int i;
+
+	/* lookup per-dimm data */
+	for (i = 0; i < ARRAY_SIZE(handle); i++)
+		if (__to_nfit_memdev(nfit_mem)->device_handle == handle[i])
+			break;
+	if (i >= ARRAY_SIZE(handle))
+		return -ENXIO;
+
+	if ((1 << func) & dimm_fail_cmd_flags[i])
+		return -EIO;
+
+	return i;
+}
+
 static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc,
 		struct nvdimm *nvdimm, unsigned int cmd, void *buf,
 		unsigned int buf_len, int *cmd_rc)
@@ -620,22 +866,39 @@  static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc,
 			func = call_pkg->nd_command;
 			if (call_pkg->nd_family != nfit_mem->family)
 				return -ENOTTY;
+
+			i = get_dimm(nfit_mem, func);
+			if (i < 0)
+				return i;
+
+			switch (func) {
+			case ND_INTEL_FW_GET_INFO:
+				return nd_intel_test_get_fw_info(t, buf,
+						buf_len, i - t->dcr_idx);
+			case ND_INTEL_FW_START_UPDATE:
+				return nd_intel_test_start_update(t, buf,
+						buf_len, i - t->dcr_idx);
+			case ND_INTEL_FW_SEND_DATA:
+				return nd_intel_test_send_data(t, buf,
+						buf_len, i - t->dcr_idx);
+			case ND_INTEL_FW_FINISH_UPDATE:
+				return nd_intel_test_finish_fw(t, buf,
+						buf_len, i - t->dcr_idx);
+			case ND_INTEL_FW_FINISH_QUERY:
+				return nd_intel_test_finish_query(t, buf,
+						buf_len, i - t->dcr_idx);
+			default:
+				return -ENOTTY;
+			}
 		}
 
 		if (!test_bit(cmd, &cmd_mask)
 				|| !test_bit(func, &nfit_mem->dsm_mask))
 			return -ENOTTY;
 
-		/* lookup per-dimm data */
-		for (i = 0; i < ARRAY_SIZE(handle); i++)
-			if (__to_nfit_memdev(nfit_mem)->device_handle ==
-					handle[i])
-				break;
-		if (i >= ARRAY_SIZE(handle))
-			return -ENXIO;
-
-		if ((1 << func) & dimm_fail_cmd_flags[i])
-			return -EIO;
+		i = get_dimm(nfit_mem, func);
+		if (i < 0)
+			return i;
 
 		switch (func) {
 		case ND_CMD_GET_CONFIG_SIZE:
@@ -1728,6 +1991,11 @@  static void nfit_test0_setup(struct nfit_test *t)
 	set_bit(NFIT_CMD_ARS_INJECT_SET, &acpi_desc->bus_nfit_cmd_force_en);
 	set_bit(NFIT_CMD_ARS_INJECT_CLEAR, &acpi_desc->bus_nfit_cmd_force_en);
 	set_bit(NFIT_CMD_ARS_INJECT_GET, &acpi_desc->bus_nfit_cmd_force_en);
+	set_bit(ND_INTEL_FW_GET_INFO, &acpi_desc->dimm_cmd_force_en);
+	set_bit(ND_INTEL_FW_START_UPDATE, &acpi_desc->dimm_cmd_force_en);
+	set_bit(ND_INTEL_FW_SEND_DATA, &acpi_desc->dimm_cmd_force_en);
+	set_bit(ND_INTEL_FW_FINISH_UPDATE, &acpi_desc->dimm_cmd_force_en);
+	set_bit(ND_INTEL_FW_FINISH_QUERY, &acpi_desc->dimm_cmd_force_en);
 }
 
 static void nfit_test1_setup(struct nfit_test *t)
@@ -2134,10 +2402,13 @@  static int nfit_test_probe(struct platform_device *pdev)
 		nfit_test->smart_threshold = devm_kcalloc(dev, num,
 				sizeof(struct nd_intel_smart_threshold),
 				GFP_KERNEL);
+		nfit_test->fw = devm_kcalloc(dev, num,
+				sizeof(struct nfit_test_fw), GFP_KERNEL);
 		if (nfit_test->dimm && nfit_test->dimm_dma && nfit_test->label
 				&& nfit_test->label_dma && nfit_test->dcr
 				&& nfit_test->dcr_dma && nfit_test->flush
-				&& nfit_test->flush_dma)
+				&& nfit_test->flush_dma
+				&& nfit_test->fw)
 			/* pass */;
 		else
 			return -ENOMEM;
diff --git a/tools/testing/nvdimm/test/nfit_test.h b/tools/testing/nvdimm/test/nfit_test.h
index ba230f6f7676..be8fa8ec0615 100644
--- a/tools/testing/nvdimm/test/nfit_test.h
+++ b/tools/testing/nvdimm/test/nfit_test.h
@@ -84,9 +84,14 @@  struct nd_cmd_ars_err_inj_stat {
 	} __packed record[0];
 } __packed;
 
-#define ND_INTEL_SMART 1
-#define ND_INTEL_SMART_THRESHOLD 2
-#define ND_INTEL_SMART_SET_THRESHOLD 17
+#define ND_INTEL_SMART			 1
+#define ND_INTEL_SMART_THRESHOLD	 2
+#define ND_INTEL_FW_GET_INFO		12
+#define ND_INTEL_FW_START_UPDATE	13
+#define ND_INTEL_FW_SEND_DATA		14
+#define ND_INTEL_FW_FINISH_UPDATE	15
+#define ND_INTEL_FW_FINISH_QUERY	16
+#define ND_INTEL_SMART_SET_THRESHOLD	17
 
 #define ND_INTEL_SMART_HEALTH_VALID             (1 << 0)
 #define ND_INTEL_SMART_SPARES_VALID             (1 << 1)
@@ -152,6 +157,61 @@  struct nd_intel_smart_set_threshold {
 	__u32 status;
 } __packed;
 
+#define INTEL_FW_STORAGE_SIZE		0x100000
+#define INTEL_FW_MAX_SEND_LEN		0xFFEC
+#define INTEL_FW_QUERY_INTERVAL		250000
+#define INTEL_FW_QUERY_MAX_TIME		3000000
+#define INTEL_FW_FIS_VERSION		0x0105
+#define INTEL_FW_FAKE_VERSION		0xffffffffabcd
+
+enum intel_fw_update_state {
+	FW_STATE_NEW = 0,
+	FW_STATE_IN_PROGRESS,
+	FW_STATE_VERIFY,
+	FW_STATE_UPDATED,
+};
+
+struct nd_intel_fw_info {
+	__u32 status;
+	__u32 storage_size;
+	__u32 max_send_len;
+	__u32 query_interval;
+	__u32 max_query_time;
+	__u8 update_cap;
+	__u8 reserved[3];
+	__u32 fis_version;
+	__u64 run_version;
+	__u64 updated_version;
+} __packed;
+
+struct nd_intel_fw_start {
+	__u32 status;
+	__u32 context;
+} __packed;
+
+/* this one has the output first because the variable input data size */
+struct nd_intel_fw_send_data {
+	__u32 context;
+	__u32 offset;
+	__u32 length;
+	__u8 data[0];
+/* this field is not declared due ot variable data from input */
+/*	__u32 status; */
+} __packed;
+
+struct nd_intel_fw_finish_update {
+	__u8 ctrl_flags;
+	__u8 reserved[3];
+	__u32 context;
+	__u32 status;
+} __packed;
+
+struct nd_intel_fw_finish_query {
+	__u32 context;
+	__u32 status;
+	__u64 updated_fw_rev;
+} __packed;
+
 union acpi_object;
 typedef void *acpi_handle;