int acpi_nfit_ctl()

in nfit/core.c [437:656]


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_desc(nd_desc);
	struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
	union acpi_object in_obj, in_buf, *out_obj;
	const struct nd_cmd_desc *desc = NULL;
	struct device *dev = acpi_desc->dev;
	struct nd_cmd_pkg *call_pkg = NULL;
	const char *cmd_name, *dimm_name;
	unsigned long cmd_mask, dsm_mask;
	u32 offset, fw_status = 0;
	acpi_handle handle;
	const guid_t *guid;
	int func, rc, i;
	int family = 0;

	if (cmd_rc)
		*cmd_rc = -EINVAL;

	if (cmd == ND_CMD_CALL)
		call_pkg = buf;
	func = cmd_to_func(nfit_mem, cmd, call_pkg, &family);
	if (func < 0)
		return func;

	if (nvdimm) {
		struct acpi_device *adev = nfit_mem->adev;

		if (!adev)
			return -ENOTTY;

		dimm_name = nvdimm_name(nvdimm);
		cmd_name = nvdimm_cmd_name(cmd);
		cmd_mask = nvdimm_cmd_mask(nvdimm);
		dsm_mask = nfit_mem->dsm_mask;
		desc = nd_cmd_dimm_desc(cmd);
		guid = to_nfit_uuid(nfit_mem->family);
		handle = adev->handle;
	} else {
		struct acpi_device *adev = to_acpi_dev(acpi_desc);

		cmd_name = nvdimm_bus_cmd_name(cmd);
		cmd_mask = nd_desc->cmd_mask;
		if (cmd == ND_CMD_CALL && call_pkg->nd_family) {
			family = call_pkg->nd_family;
			if (family > NVDIMM_BUS_FAMILY_MAX ||
			    !test_bit(family, &nd_desc->bus_family_mask))
				return -EINVAL;
			family = array_index_nospec(family,
						    NVDIMM_BUS_FAMILY_MAX + 1);
			dsm_mask = acpi_desc->family_dsm_mask[family];
			guid = to_nfit_bus_uuid(family);
		} else {
			dsm_mask = acpi_desc->bus_dsm_mask;
			guid = to_nfit_uuid(NFIT_DEV_BUS);
		}
		desc = nd_cmd_bus_desc(cmd);
		handle = adev->handle;
		dimm_name = "bus";
	}

	if (!desc || (cmd && (desc->out_num + desc->in_num == 0)))
		return -ENOTTY;

	/*
	 * Check for a valid command.  For ND_CMD_CALL, we also have to
	 * make sure that the DSM function is supported.
	 */
	if (cmd == ND_CMD_CALL &&
	    (func > NVDIMM_CMD_MAX || !test_bit(func, &dsm_mask)))
		return -ENOTTY;
	else if (!test_bit(cmd, &cmd_mask))
		return -ENOTTY;

	in_obj.type = ACPI_TYPE_PACKAGE;
	in_obj.package.count = 1;
	in_obj.package.elements = &in_buf;
	in_buf.type = ACPI_TYPE_BUFFER;
	in_buf.buffer.pointer = buf;
	in_buf.buffer.length = 0;

	/* libnvdimm has already validated the input envelope */
	for (i = 0; i < desc->in_num; i++)
		in_buf.buffer.length += nd_cmd_in_size(nvdimm, cmd, desc,
				i, buf);

	if (call_pkg) {
		/* skip over package wrapper */
		in_buf.buffer.pointer = (void *) &call_pkg->nd_payload;
		in_buf.buffer.length = call_pkg->nd_size_in;
	}

	dev_dbg(dev, "%s cmd: %d: family: %d func: %d input length: %d\n",
		dimm_name, cmd, family, func, in_buf.buffer.length);
	if (payload_dumpable(nvdimm, func))
		print_hex_dump_debug("nvdimm in  ", DUMP_PREFIX_OFFSET, 4, 4,
				in_buf.buffer.pointer,
				min_t(u32, 256, in_buf.buffer.length), true);

	/* call the BIOS, prefer the named methods over _DSM if available */
	if (nvdimm && cmd == ND_CMD_GET_CONFIG_SIZE
			&& test_bit(NFIT_MEM_LSR, &nfit_mem->flags))
		out_obj = acpi_label_info(handle);
	else if (nvdimm && cmd == ND_CMD_GET_CONFIG_DATA
			&& test_bit(NFIT_MEM_LSR, &nfit_mem->flags)) {
		struct nd_cmd_get_config_data_hdr *p = buf;

		out_obj = acpi_label_read(handle, p->in_offset, p->in_length);
	} else if (nvdimm && cmd == ND_CMD_SET_CONFIG_DATA
			&& test_bit(NFIT_MEM_LSW, &nfit_mem->flags)) {
		struct nd_cmd_set_config_hdr *p = buf;

		out_obj = acpi_label_write(handle, p->in_offset, p->in_length,
				p->in_buf);
	} else {
		u8 revid;

		if (nvdimm)
			revid = nfit_dsm_revid(nfit_mem->family, func);
		else
			revid = 1;
		out_obj = acpi_evaluate_dsm(handle, guid, revid, func, &in_obj);
	}

	if (!out_obj) {
		dev_dbg(dev, "%s _DSM failed cmd: %s\n", dimm_name, cmd_name);
		return -EINVAL;
	}

	if (out_obj->type != ACPI_TYPE_BUFFER) {
		dev_dbg(dev, "%s unexpected output object type cmd: %s type: %d\n",
				dimm_name, cmd_name, out_obj->type);
		rc = -EINVAL;
		goto out;
	}

	dev_dbg(dev, "%s cmd: %s output length: %d\n", dimm_name,
			cmd_name, out_obj->buffer.length);
	print_hex_dump_debug(cmd_name, DUMP_PREFIX_OFFSET, 4, 4,
			out_obj->buffer.pointer,
			min_t(u32, 128, out_obj->buffer.length), true);

	if (call_pkg) {
		call_pkg->nd_fw_size = out_obj->buffer.length;
		memcpy(call_pkg->nd_payload + call_pkg->nd_size_in,
			out_obj->buffer.pointer,
			min(call_pkg->nd_fw_size, call_pkg->nd_size_out));

		ACPI_FREE(out_obj);
		/*
		 * Need to support FW function w/o known size in advance.
		 * Caller can determine required size based upon nd_fw_size.
		 * If we return an error (like elsewhere) then caller wouldn't
		 * be able to rely upon data returned to make calculation.
		 */
		if (cmd_rc)
			*cmd_rc = 0;
		return 0;
	}

	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,
				out_obj->buffer.length - offset);

		if (offset + out_size > out_obj->buffer.length) {
			dev_dbg(dev, "%s output object underflow cmd: %s field: %d\n",
					dimm_name, cmd_name, i);
			break;
		}

		if (in_buf.buffer.length + offset + out_size > buf_len) {
			dev_dbg(dev, "%s output overrun cmd: %s field: %d\n",
					dimm_name, cmd_name, i);
			rc = -ENXIO;
			goto out;
		}
		memcpy(buf + in_buf.buffer.length + offset,
				out_obj->buffer.pointer + offset, out_size);
		offset += out_size;
	}

	/*
	 * Set fw_status for all the commands with a known format to be
	 * later interpreted by xlat_status().
	 */
	if (i >= 1 && ((!nvdimm && cmd >= ND_CMD_ARS_CAP
					&& cmd <= ND_CMD_CLEAR_ERROR)
				|| (nvdimm && cmd >= ND_CMD_SMART
					&& cmd <= ND_CMD_VENDOR)))
		fw_status = *(u32 *) out_obj->buffer.pointer;

	if (offset + in_buf.buffer.length < buf_len) {
		if (i >= 1) {
			/*
			 * status valid, return the number of bytes left
			 * unfilled in the output buffer
			 */
			rc = buf_len - offset - in_buf.buffer.length;
			if (cmd_rc)
				*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,
					offset);
			rc = -ENXIO;
		}
	} else {
		rc = 0;
		if (cmd_rc)
			*cmd_rc = xlat_status(nvdimm, buf, cmd, fw_status);
	}

 out:
	ACPI_FREE(out_obj);

	return rc;
}