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;
}