Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 810ac7b7 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull libnvdimm fixes from Dan Williams:
 "Several fixes to the DSM (ACPI device specific method) marshaling
  implementation.

  I consider these urgent enough to send for 4.9 consideration since
  they fix the kernel's handling of ARS (Address Range Scrub) commands.
  Especially for platforms without machine-check-recovery capabilities,
  successful execution of ARS commands enables the platform to
  potentially break out of an infinite reboot problem if a media error
  is present in the boot path. There is also a one line fix for a
  device-dax read-only mapping regression.

  Commits 9a901f54 ("acpi, nfit: fix extended status translations
  for ACPI DSMs") and 325896ff ("device-dax: fix private mapping
  restriction, permit read-only") are true regression fixes for changes
  introduced this cycle.

  Commit efda1b5d ("acpi, nfit, libnvdimm: fix / harden ars_status
  output length handling") fixes the kernel's handling of zero-length
  results, this never would have worked in the past, but we only just
  recently discovered a BIOS implementation that emits this arguably
  spec non-compliant result.

  The remaining two commits are additional fall out from thinking
  through the implications of a zero / truncated length result of the
  ARS Status command.

  In order to mitigate the risk that these changes introduce yet more
  regressions they are backstopped by a new unit test in commit
  a7de92da ("tools/testing/nvdimm: unit test acpi_nfit_ctl()") that
  mocks up inputs to acpi_nfit_ctl()"

* 'libnvdimm-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm:
  device-dax: fix private mapping restriction, permit read-only
  tools/testing/nvdimm: unit test acpi_nfit_ctl()
  acpi, nfit: fix bus vs dimm confusion in xlat_status
  acpi, nfit: validate ars_status output buffer size
  acpi, nfit, libnvdimm: fix / harden ars_status output length handling
  acpi, nfit: fix extended status translations for ACPI DSMs
parents 861d75d0 325896ff
Loading
Loading
Loading
Loading
+40 −15
Original line number Diff line number Diff line
@@ -94,7 +94,7 @@ static struct acpi_device *to_acpi_dev(struct acpi_nfit_desc *acpi_desc)
	return to_acpi_device(acpi_desc->dev);
}

static int xlat_status(void *buf, unsigned int cmd, u32 status)
static int xlat_bus_status(void *buf, unsigned int cmd, u32 status)
{
	struct nd_cmd_clear_error *clear_err;
	struct nd_cmd_ars_status *ars_status;
@@ -113,7 +113,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
		flags = ND_ARS_PERSISTENT | ND_ARS_VOLATILE;
		if ((status >> 16 & flags) == 0)
			return -ENOTTY;
		break;
		return 0;
	case ND_CMD_ARS_START:
		/* ARS is in progress */
		if ((status & 0xffff) == NFIT_ARS_START_BUSY)
@@ -122,7 +122,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
		/* Command failed */
		if (status & 0xffff)
			return -EIO;
		break;
		return 0;
	case ND_CMD_ARS_STATUS:
		ars_status = buf;
		/* Command failed */
@@ -146,7 +146,8 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
		 * then just continue with the returned results.
		 */
		if (status == NFIT_ARS_STATUS_INTR) {
			if (ars_status->flags & NFIT_ARS_F_OVERFLOW)
			if (ars_status->out_length >= 40 && (ars_status->flags
						& NFIT_ARS_F_OVERFLOW))
				return -ENOSPC;
			return 0;
		}
@@ -154,7 +155,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
		/* Unknown status */
		if (status >> 16)
			return -EIO;
		break;
		return 0;
	case ND_CMD_CLEAR_ERROR:
		clear_err = buf;
		if (status & 0xffff)
@@ -163,7 +164,7 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
			return -EIO;
		if (clear_err->length > clear_err->cleared)
			return clear_err->cleared;
		break;
		return 0;
	default:
		break;
	}
@@ -174,9 +175,18 @@ static int xlat_status(void *buf, unsigned int cmd, u32 status)
	return 0;
}

static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
		struct nvdimm *nvdimm, unsigned int cmd, void *buf,
		unsigned int buf_len, int *cmd_rc)
static int xlat_status(struct nvdimm *nvdimm, void *buf, unsigned int cmd,
		u32 status)
{
	if (!nvdimm)
		return xlat_bus_status(buf, cmd, status);
	if (status)
		return -EIO;
	return 0;
}

int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
		unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc)
{
	struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
	union acpi_object in_obj, in_buf, *out_obj;
@@ -298,7 +308,8 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,

	for (i = 0, offset = 0; i < desc->out_num; i++) {
		u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i, buf,
				(u32 *) out_obj->buffer.pointer);
				(u32 *) out_obj->buffer.pointer,
				out_obj->buffer.length - offset);

		if (offset + out_size > out_obj->buffer.length) {
			dev_dbg(dev, "%s:%s output object underflow cmd: %s field: %d\n",
@@ -333,7 +344,8 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
			 */
			rc = buf_len - offset - in_buf.buffer.length;
			if (cmd_rc)
				*cmd_rc = xlat_status(buf, cmd, fw_status);
				*cmd_rc = xlat_status(nvdimm, buf, cmd,
						fw_status);
		} else {
			dev_err(dev, "%s:%s underrun cmd: %s buf_len: %d out_len: %d\n",
					__func__, dimm_name, cmd_name, buf_len,
@@ -343,7 +355,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
	} else {
		rc = 0;
		if (cmd_rc)
			*cmd_rc = xlat_status(buf, cmd, fw_status);
			*cmd_rc = xlat_status(nvdimm, buf, cmd, fw_status);
	}

 out:
@@ -351,6 +363,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,

	return rc;
}
EXPORT_SYMBOL_GPL(acpi_nfit_ctl);

static const char *spa_type_name(u16 type)
{
@@ -2001,19 +2014,32 @@ static int ars_get_status(struct acpi_nfit_desc *acpi_desc)
	return cmd_rc;
}

static int ars_status_process_records(struct nvdimm_bus *nvdimm_bus,
static int ars_status_process_records(struct acpi_nfit_desc *acpi_desc,
		struct nd_cmd_ars_status *ars_status)
{
	struct nvdimm_bus *nvdimm_bus = acpi_desc->nvdimm_bus;
	int rc;
	u32 i;

	/*
	 * First record starts at 44 byte offset from the start of the
	 * payload.
	 */
	if (ars_status->out_length < 44)
		return 0;
	for (i = 0; i < ars_status->num_records; i++) {
		/* only process full records */
		if (ars_status->out_length
				< 44 + sizeof(struct nd_ars_record) * (i + 1))
			break;
		rc = nvdimm_bus_add_poison(nvdimm_bus,
				ars_status->records[i].err_address,
				ars_status->records[i].length);
		if (rc)
			return rc;
	}
	if (i < ars_status->num_records)
		dev_warn(acpi_desc->dev, "detected truncated ars results\n");

	return 0;
}
@@ -2266,8 +2292,7 @@ static int acpi_nfit_query_poison(struct acpi_nfit_desc *acpi_desc,
	if (rc < 0 && rc != -ENOSPC)
		return rc;

	if (ars_status_process_records(acpi_desc->nvdimm_bus,
				acpi_desc->ars_status))
	if (ars_status_process_records(acpi_desc, acpi_desc->ars_status))
		return -ENOMEM;

	return 0;
+2 −0
Original line number Diff line number Diff line
@@ -240,5 +240,7 @@ const u8 *to_nfit_uuid(enum nfit_uuids id);
int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *nfit, acpi_size sz);
void __acpi_nfit_notify(struct device *dev, acpi_handle handle, u32 event);
void __acpi_nvdimm_notify(struct device *dev, u32 event);
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
		unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc);
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
#endif /* __NFIT_H__ */
+1 −1
Original line number Diff line number Diff line
@@ -271,7 +271,7 @@ static int check_vma(struct dax_dev *dax_dev, struct vm_area_struct *vma,
		return -ENXIO;

	/* prevent private mappings from being established */
	if ((vma->vm_flags & VM_SHARED) != VM_SHARED) {
	if ((vma->vm_flags & VM_MAYSHARE) != VM_MAYSHARE) {
		dev_info(dev, "%s: %s: fail, attempted private mapping\n",
				current->comm, func);
		return -EINVAL;
+20 −5
Original line number Diff line number Diff line
@@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(nd_cmd_in_size);

u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
		const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
		const u32 *out_field)
		const u32 *out_field, unsigned long remainder)
{
	if (idx >= desc->out_num)
		return UINT_MAX;
@@ -727,9 +727,24 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
		return in_field[1];
	else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
		return out_field[1];
	else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
		return out_field[1] - 8;
	else if (cmd == ND_CMD_CALL) {
	else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2) {
		/*
		 * Per table 9-276 ARS Data in ACPI 6.1, out_field[1] is
		 * "Size of Output Buffer in bytes, including this
		 * field."
		 */
		if (out_field[1] < 4)
			return 0;
		/*
		 * ACPI 6.1 is ambiguous if 'status' is included in the
		 * output size. If we encounter an output size that
		 * overshoots the remainder by 4 bytes, assume it was
		 * including 'status'.
		 */
		if (out_field[1] - 8 == remainder)
			return remainder;
		return out_field[1] - 4;
	} else if (cmd == ND_CMD_CALL) {
		struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;

		return pkg->nd_size_out;
@@ -876,7 +891,7 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
	/* process an output envelope */
	for (i = 0; i < desc->out_num; i++) {
		u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i,
				(u32 *) in_env, (u32 *) out_env);
				(u32 *) in_env, (u32 *) out_env, 0);
		u32 copy;

		if (out_size == UINT_MAX) {
+1 −1
Original line number Diff line number Diff line
@@ -143,7 +143,7 @@ u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
		const struct nd_cmd_desc *desc, int idx, void *buf);
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
		const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
		const u32 *out_field);
		const u32 *out_field, unsigned long remainder);
int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count);
struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
		struct nd_region_desc *ndr_desc);
Loading