|
@@ -45,6 +45,11 @@ module_param(scrub_overflow_abort, uint, S_IRUGO|S_IWUSR);
|
|
MODULE_PARM_DESC(scrub_overflow_abort,
|
|
MODULE_PARM_DESC(scrub_overflow_abort,
|
|
"Number of times we overflow ARS results before abort");
|
|
"Number of times we overflow ARS results before abort");
|
|
|
|
|
|
|
|
+static bool disable_vendor_specific;
|
|
|
|
+module_param(disable_vendor_specific, bool, S_IRUGO);
|
|
|
|
+MODULE_PARM_DESC(disable_vendor_specific,
|
|
|
|
+ "Limit commands to the publicly specified set\n");
|
|
|
|
+
|
|
static struct workqueue_struct *nfit_wq;
|
|
static struct workqueue_struct *nfit_wq;
|
|
|
|
|
|
struct nfit_table_prev {
|
|
struct nfit_table_prev {
|
|
@@ -171,33 +176,46 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|
unsigned int buf_len, int *cmd_rc)
|
|
unsigned int buf_len, int *cmd_rc)
|
|
{
|
|
{
|
|
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
|
|
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
|
|
- const struct nd_cmd_desc *desc = NULL;
|
|
|
|
union acpi_object in_obj, in_buf, *out_obj;
|
|
union acpi_object in_obj, in_buf, *out_obj;
|
|
|
|
+ const struct nd_cmd_desc *desc = NULL;
|
|
struct device *dev = acpi_desc->dev;
|
|
struct device *dev = acpi_desc->dev;
|
|
|
|
+ struct nd_cmd_pkg *call_pkg = NULL;
|
|
const char *cmd_name, *dimm_name;
|
|
const char *cmd_name, *dimm_name;
|
|
- unsigned long dsm_mask;
|
|
|
|
|
|
+ unsigned long cmd_mask, dsm_mask;
|
|
acpi_handle handle;
|
|
acpi_handle handle;
|
|
|
|
+ unsigned int func;
|
|
const u8 *uuid;
|
|
const u8 *uuid;
|
|
u32 offset;
|
|
u32 offset;
|
|
int rc, i;
|
|
int rc, i;
|
|
|
|
|
|
|
|
+ func = cmd;
|
|
|
|
+ if (cmd == ND_CMD_CALL) {
|
|
|
|
+ call_pkg = buf;
|
|
|
|
+ func = call_pkg->nd_command;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (nvdimm) {
|
|
if (nvdimm) {
|
|
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
|
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
|
struct acpi_device *adev = nfit_mem->adev;
|
|
struct acpi_device *adev = nfit_mem->adev;
|
|
|
|
|
|
if (!adev)
|
|
if (!adev)
|
|
return -ENOTTY;
|
|
return -ENOTTY;
|
|
|
|
+ if (call_pkg && nfit_mem->family != call_pkg->nd_family)
|
|
|
|
+ return -ENOTTY;
|
|
|
|
+
|
|
dimm_name = nvdimm_name(nvdimm);
|
|
dimm_name = nvdimm_name(nvdimm);
|
|
cmd_name = nvdimm_cmd_name(cmd);
|
|
cmd_name = nvdimm_cmd_name(cmd);
|
|
|
|
+ cmd_mask = nvdimm_cmd_mask(nvdimm);
|
|
dsm_mask = nfit_mem->dsm_mask;
|
|
dsm_mask = nfit_mem->dsm_mask;
|
|
desc = nd_cmd_dimm_desc(cmd);
|
|
desc = nd_cmd_dimm_desc(cmd);
|
|
- uuid = to_nfit_uuid(NFIT_DEV_DIMM);
|
|
|
|
|
|
+ uuid = to_nfit_uuid(nfit_mem->family);
|
|
handle = adev->handle;
|
|
handle = adev->handle;
|
|
} else {
|
|
} else {
|
|
struct acpi_device *adev = to_acpi_dev(acpi_desc);
|
|
struct acpi_device *adev = to_acpi_dev(acpi_desc);
|
|
|
|
|
|
cmd_name = nvdimm_bus_cmd_name(cmd);
|
|
cmd_name = nvdimm_bus_cmd_name(cmd);
|
|
- dsm_mask = nd_desc->dsm_mask;
|
|
|
|
|
|
+ cmd_mask = nd_desc->cmd_mask;
|
|
|
|
+ dsm_mask = cmd_mask;
|
|
desc = nd_cmd_bus_desc(cmd);
|
|
desc = nd_cmd_bus_desc(cmd);
|
|
uuid = to_nfit_uuid(NFIT_DEV_BUS);
|
|
uuid = to_nfit_uuid(NFIT_DEV_BUS);
|
|
handle = adev->handle;
|
|
handle = adev->handle;
|
|
@@ -207,7 +225,7 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|
if (!desc || (cmd && (desc->out_num + desc->in_num == 0)))
|
|
if (!desc || (cmd && (desc->out_num + desc->in_num == 0)))
|
|
return -ENOTTY;
|
|
return -ENOTTY;
|
|
|
|
|
|
- if (!test_bit(cmd, &dsm_mask))
|
|
|
|
|
|
+ if (!test_bit(cmd, &cmd_mask) || !test_bit(func, &dsm_mask))
|
|
return -ENOTTY;
|
|
return -ENOTTY;
|
|
|
|
|
|
in_obj.type = ACPI_TYPE_PACKAGE;
|
|
in_obj.type = ACPI_TYPE_PACKAGE;
|
|
@@ -222,21 +240,44 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|
in_buf.buffer.length += nd_cmd_in_size(nvdimm, cmd, desc,
|
|
in_buf.buffer.length += nd_cmd_in_size(nvdimm, cmd, desc,
|
|
i, buf);
|
|
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;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (IS_ENABLED(CONFIG_ACPI_NFIT_DEBUG)) {
|
|
if (IS_ENABLED(CONFIG_ACPI_NFIT_DEBUG)) {
|
|
- dev_dbg(dev, "%s:%s cmd: %s input length: %d\n", __func__,
|
|
|
|
- dimm_name, cmd_name, in_buf.buffer.length);
|
|
|
|
- print_hex_dump_debug(cmd_name, DUMP_PREFIX_OFFSET, 4,
|
|
|
|
- 4, in_buf.buffer.pointer, min_t(u32, 128,
|
|
|
|
- in_buf.buffer.length), true);
|
|
|
|
|
|
+ dev_dbg(dev, "%s:%s cmd: %d: func: %d input length: %d\n",
|
|
|
|
+ __func__, dimm_name, cmd, func,
|
|
|
|
+ in_buf.buffer.length);
|
|
|
|
+ print_hex_dump_debug("nvdimm in ", DUMP_PREFIX_OFFSET, 4, 4,
|
|
|
|
+ in_buf.buffer.pointer,
|
|
|
|
+ min_t(u32, 256, in_buf.buffer.length), true);
|
|
}
|
|
}
|
|
|
|
|
|
- out_obj = acpi_evaluate_dsm(handle, uuid, 1, cmd, &in_obj);
|
|
|
|
|
|
+ out_obj = acpi_evaluate_dsm(handle, uuid, 1, func, &in_obj);
|
|
if (!out_obj) {
|
|
if (!out_obj) {
|
|
dev_dbg(dev, "%s:%s _DSM failed cmd: %s\n", __func__, dimm_name,
|
|
dev_dbg(dev, "%s:%s _DSM failed cmd: %s\n", __func__, dimm_name,
|
|
cmd_name);
|
|
cmd_name);
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ 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.
|
|
|
|
+ */
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (out_obj->package.type != ACPI_TYPE_BUFFER) {
|
|
if (out_obj->package.type != ACPI_TYPE_BUFFER) {
|
|
dev_dbg(dev, "%s:%s unexpected output object type cmd: %s type: %d\n",
|
|
dev_dbg(dev, "%s:%s unexpected output object type cmd: %s type: %d\n",
|
|
__func__, dimm_name, cmd_name, out_obj->type);
|
|
__func__, dimm_name, cmd_name, out_obj->type);
|
|
@@ -921,6 +962,30 @@ static ssize_t serial_show(struct device *dev,
|
|
}
|
|
}
|
|
static DEVICE_ATTR_RO(serial);
|
|
static DEVICE_ATTR_RO(serial);
|
|
|
|
|
|
|
|
+static ssize_t family_show(struct device *dev,
|
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
|
+{
|
|
|
|
+ struct nvdimm *nvdimm = to_nvdimm(dev);
|
|
|
|
+ struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
|
|
|
+
|
|
|
|
+ if (nfit_mem->family < 0)
|
|
|
|
+ return -ENXIO;
|
|
|
|
+ return sprintf(buf, "%d\n", nfit_mem->family);
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR_RO(family);
|
|
|
|
+
|
|
|
|
+static ssize_t dsm_mask_show(struct device *dev,
|
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
|
+{
|
|
|
|
+ struct nvdimm *nvdimm = to_nvdimm(dev);
|
|
|
|
+ struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
|
|
|
+
|
|
|
|
+ if (nfit_mem->family < 0)
|
|
|
|
+ return -ENXIO;
|
|
|
|
+ return sprintf(buf, "%#lx\n", nfit_mem->dsm_mask);
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR_RO(dsm_mask);
|
|
|
|
+
|
|
static ssize_t flags_show(struct device *dev,
|
|
static ssize_t flags_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
{
|
|
@@ -946,6 +1011,8 @@ static struct attribute *acpi_nfit_dimm_attributes[] = {
|
|
&dev_attr_serial.attr,
|
|
&dev_attr_serial.attr,
|
|
&dev_attr_rev_id.attr,
|
|
&dev_attr_rev_id.attr,
|
|
&dev_attr_flags.attr,
|
|
&dev_attr_flags.attr,
|
|
|
|
+ &dev_attr_family.attr,
|
|
|
|
+ &dev_attr_dsm_mask.attr,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
};
|
|
|
|
|
|
@@ -992,10 +1059,13 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
|
|
{
|
|
{
|
|
struct acpi_device *adev, *adev_dimm;
|
|
struct acpi_device *adev, *adev_dimm;
|
|
struct device *dev = acpi_desc->dev;
|
|
struct device *dev = acpi_desc->dev;
|
|
- const u8 *uuid = to_nfit_uuid(NFIT_DEV_DIMM);
|
|
|
|
|
|
+ unsigned long dsm_mask;
|
|
|
|
+ const u8 *uuid;
|
|
int i;
|
|
int i;
|
|
|
|
|
|
- nfit_mem->dsm_mask = acpi_desc->dimm_dsm_force_en;
|
|
|
|
|
|
+ /* nfit test assumes 1:1 relationship between commands and dsms */
|
|
|
|
+ nfit_mem->dsm_mask = acpi_desc->dimm_cmd_force_en;
|
|
|
|
+ nfit_mem->family = NVDIMM_FAMILY_INTEL;
|
|
adev = to_acpi_dev(acpi_desc);
|
|
adev = to_acpi_dev(acpi_desc);
|
|
if (!adev)
|
|
if (!adev)
|
|
return 0;
|
|
return 0;
|
|
@@ -1008,7 +1078,35 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
|
|
return force_enable_dimms ? 0 : -ENODEV;
|
|
return force_enable_dimms ? 0 : -ENODEV;
|
|
}
|
|
}
|
|
|
|
|
|
- for (i = ND_CMD_SMART; i <= ND_CMD_VENDOR; i++)
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Until standardization materializes we need to consider up to 3
|
|
|
|
+ * different command sets. Note, that checking for function0 (bit0)
|
|
|
|
+ * tells us if any commands are reachable through this uuid.
|
|
|
|
+ */
|
|
|
|
+ for (i = NVDIMM_FAMILY_INTEL; i <= NVDIMM_FAMILY_HPE2; i++)
|
|
|
|
+ if (acpi_check_dsm(adev_dimm->handle, to_nfit_uuid(i), 1, 1))
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ /* limit the supported commands to those that are publicly documented */
|
|
|
|
+ nfit_mem->family = i;
|
|
|
|
+ if (nfit_mem->family == NVDIMM_FAMILY_INTEL) {
|
|
|
|
+ dsm_mask = 0x3fe;
|
|
|
|
+ if (disable_vendor_specific)
|
|
|
|
+ dsm_mask &= ~(1 << ND_CMD_VENDOR);
|
|
|
|
+ } else if (nfit_mem->family == NVDIMM_FAMILY_HPE1)
|
|
|
|
+ dsm_mask = 0x1c3c76;
|
|
|
|
+ else if (nfit_mem->family == NVDIMM_FAMILY_HPE2) {
|
|
|
|
+ dsm_mask = 0x1fe;
|
|
|
|
+ if (disable_vendor_specific)
|
|
|
|
+ dsm_mask &= ~(1 << 8);
|
|
|
|
+ } else {
|
|
|
|
+ dev_err(dev, "unknown dimm command family\n");
|
|
|
|
+ nfit_mem->family = -1;
|
|
|
|
+ return force_enable_dimms ? 0 : -ENODEV;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ uuid = to_nfit_uuid(nfit_mem->family);
|
|
|
|
+ for_each_set_bit(i, &dsm_mask, BITS_PER_LONG)
|
|
if (acpi_check_dsm(adev_dimm->handle, uuid, 1, 1ULL << i))
|
|
if (acpi_check_dsm(adev_dimm->handle, uuid, 1, 1ULL << i))
|
|
set_bit(i, &nfit_mem->dsm_mask);
|
|
set_bit(i, &nfit_mem->dsm_mask);
|
|
|
|
|
|
@@ -1021,8 +1119,8 @@ static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
|
|
int dimm_count = 0;
|
|
int dimm_count = 0;
|
|
|
|
|
|
list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
|
|
list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
|
|
|
|
+ unsigned long flags = 0, cmd_mask;
|
|
struct nvdimm *nvdimm;
|
|
struct nvdimm *nvdimm;
|
|
- unsigned long flags = 0;
|
|
|
|
u32 device_handle;
|
|
u32 device_handle;
|
|
u16 mem_flags;
|
|
u16 mem_flags;
|
|
int rc;
|
|
int rc;
|
|
@@ -1045,9 +1143,18 @@ static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
|
|
if (rc)
|
|
if (rc)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * TODO: provide translation for non-NVDIMM_FAMILY_INTEL
|
|
|
|
+ * devices (i.e. from nd_cmd to acpi_dsm) to standardize the
|
|
|
|
+ * userspace interface.
|
|
|
|
+ */
|
|
|
|
+ cmd_mask = 1UL << ND_CMD_CALL;
|
|
|
|
+ if (nfit_mem->family == NVDIMM_FAMILY_INTEL)
|
|
|
|
+ cmd_mask |= nfit_mem->dsm_mask;
|
|
|
|
+
|
|
nvdimm = nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,
|
|
nvdimm = nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,
|
|
acpi_nfit_dimm_attribute_groups,
|
|
acpi_nfit_dimm_attribute_groups,
|
|
- flags, &nfit_mem->dsm_mask);
|
|
|
|
|
|
+ flags, cmd_mask);
|
|
if (!nvdimm)
|
|
if (!nvdimm)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
@@ -1076,14 +1183,14 @@ static void acpi_nfit_init_dsms(struct acpi_nfit_desc *acpi_desc)
|
|
struct acpi_device *adev;
|
|
struct acpi_device *adev;
|
|
int i;
|
|
int i;
|
|
|
|
|
|
- nd_desc->dsm_mask = acpi_desc->bus_dsm_force_en;
|
|
|
|
|
|
+ nd_desc->cmd_mask = acpi_desc->bus_cmd_force_en;
|
|
adev = to_acpi_dev(acpi_desc);
|
|
adev = to_acpi_dev(acpi_desc);
|
|
if (!adev)
|
|
if (!adev)
|
|
return;
|
|
return;
|
|
|
|
|
|
for (i = ND_CMD_ARS_CAP; i <= ND_CMD_CLEAR_ERROR; i++)
|
|
for (i = ND_CMD_ARS_CAP; i <= ND_CMD_CLEAR_ERROR; i++)
|
|
if (acpi_check_dsm(adev->handle, uuid, 1, 1ULL << i))
|
|
if (acpi_check_dsm(adev->handle, uuid, 1, 1ULL << i))
|
|
- set_bit(i, &nd_desc->dsm_mask);
|
|
|
|
|
|
+ set_bit(i, &nd_desc->cmd_mask);
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t range_index_show(struct device *dev,
|
|
static ssize_t range_index_show(struct device *dev,
|
|
@@ -2532,6 +2639,8 @@ static __init int nfit_init(void)
|
|
acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_CD, nfit_uuid[NFIT_SPA_PCD]);
|
|
acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_CD, nfit_uuid[NFIT_SPA_PCD]);
|
|
acpi_str_to_uuid(UUID_NFIT_BUS, nfit_uuid[NFIT_DEV_BUS]);
|
|
acpi_str_to_uuid(UUID_NFIT_BUS, nfit_uuid[NFIT_DEV_BUS]);
|
|
acpi_str_to_uuid(UUID_NFIT_DIMM, nfit_uuid[NFIT_DEV_DIMM]);
|
|
acpi_str_to_uuid(UUID_NFIT_DIMM, nfit_uuid[NFIT_DEV_DIMM]);
|
|
|
|
+ acpi_str_to_uuid(UUID_NFIT_DIMM_N_HPE1, nfit_uuid[NFIT_DEV_DIMM_N_HPE1]);
|
|
|
|
+ acpi_str_to_uuid(UUID_NFIT_DIMM_N_HPE2, nfit_uuid[NFIT_DEV_DIMM_N_HPE2]);
|
|
|
|
|
|
nfit_wq = create_singlethread_workqueue("nfit");
|
|
nfit_wq = create_singlethread_workqueue("nfit");
|
|
if (!nfit_wq)
|
|
if (!nfit_wq)
|