return 0;
 }
 
-static int xlat_nvdimm_status(void *buf, unsigned int cmd, u32 status)
+#define ACPI_LABELS_LOCKED 3
+
+static int xlat_nvdimm_status(struct nvdimm *nvdimm, void *buf, unsigned int cmd,
+               u32 status)
 {
+       struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
+
        switch (cmd) {
        case ND_CMD_GET_CONFIG_SIZE:
+               /*
+                * In the _LSI, _LSR, _LSW case the locked status is
+                * communicated via the read/write commands
+                */
+               if (nfit_mem->has_lsi)
+                       break;
+
                if (status >> 16 & ND_CONFIG_LOCKED)
                        return -EACCES;
                break;
+       case ND_CMD_GET_CONFIG_DATA:
+               if (nfit_mem->has_lsr && status == ACPI_LABELS_LOCKED)
+                       return -EACCES;
+               break;
+       case ND_CMD_SET_CONFIG_DATA:
+               if (nfit_mem->has_lsw && status == ACPI_LABELS_LOCKED)
+                       return -EACCES;
+               break;
        default:
                break;
        }
 {
        if (!nvdimm)
                return xlat_bus_status(buf, cmd, status);
-       return xlat_nvdimm_status(buf, cmd, status);
+       return xlat_nvdimm_status(nvdimm, buf, cmd, status);
+}
+
+/* convert _LS{I,R} packages to the buffer object acpi_nfit_ctl expects */
+static union acpi_object *pkg_to_buf(union acpi_object *pkg)
+{
+       int i;
+       void *dst;
+       size_t size = 0;
+       union acpi_object *buf = NULL;
+
+       if (pkg->type != ACPI_TYPE_PACKAGE) {
+               WARN_ONCE(1, "BIOS bug, unexpected element type: %d\n",
+                               pkg->type);
+               goto err;
+       }
+
+       for (i = 0; i < pkg->package.count; i++) {
+               union acpi_object *obj = &pkg->package.elements[i];
+
+               if (obj->type == ACPI_TYPE_INTEGER)
+                       size += 4;
+               else if (obj->type == ACPI_TYPE_BUFFER)
+                       size += obj->buffer.length;
+               else {
+                       WARN_ONCE(1, "BIOS bug, unexpected element type: %d\n",
+                                       obj->type);
+                       goto err;
+               }
+       }
+
+       buf = ACPI_ALLOCATE(sizeof(*buf) + size);
+       if (!buf)
+               goto err;
+
+       dst = buf + 1;
+       buf->type = ACPI_TYPE_BUFFER;
+       buf->buffer.length = size;
+       buf->buffer.pointer = dst;
+       for (i = 0; i < pkg->package.count; i++) {
+               union acpi_object *obj = &pkg->package.elements[i];
+
+               if (obj->type == ACPI_TYPE_INTEGER) {
+                       memcpy(dst, &obj->integer.value, 4);
+                       dst += 4;
+               } else if (obj->type == ACPI_TYPE_BUFFER) {
+                       memcpy(dst, obj->buffer.pointer, obj->buffer.length);
+                       dst += obj->buffer.length;
+               }
+       }
+err:
+       ACPI_FREE(pkg);
+       return buf;
+}
+
+static union acpi_object *int_to_buf(union acpi_object *integer)
+{
+       union acpi_object *buf = ACPI_ALLOCATE(sizeof(*buf) + 4);
+       void *dst = NULL;
+
+       if (!buf)
+               goto err;
+
+       if (integer->type != ACPI_TYPE_INTEGER) {
+               WARN_ONCE(1, "BIOS bug, unexpected element type: %d\n",
+                               integer->type);
+               goto err;
+       }
+
+       dst = buf + 1;
+       buf->type = ACPI_TYPE_BUFFER;
+       buf->buffer.length = 4;
+       buf->buffer.pointer = dst;
+       memcpy(dst, &integer->integer.value, 4);
+err:
+       ACPI_FREE(integer);
+       return buf;
+}
+
+static union acpi_object *acpi_label_write(acpi_handle handle, u32 offset,
+               u32 len, void *data)
+{
+       acpi_status rc;
+       struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+       struct acpi_object_list input = {
+               .count = 3,
+               .pointer = (union acpi_object []) {
+                       [0] = {
+                               .integer.type = ACPI_TYPE_INTEGER,
+                               .integer.value = offset,
+                       },
+                       [1] = {
+                               .integer.type = ACPI_TYPE_INTEGER,
+                               .integer.value = len,
+                       },
+                       [2] = {
+                               .buffer.type = ACPI_TYPE_BUFFER,
+                               .buffer.pointer = data,
+                               .buffer.length = len,
+                       },
+               },
+       };
+
+       rc = acpi_evaluate_object(handle, "_LSW", &input, &buf);
+       if (ACPI_FAILURE(rc))
+               return NULL;
+       return int_to_buf(buf.pointer);
+}
+
+static union acpi_object *acpi_label_read(acpi_handle handle, u32 offset,
+               u32 len)
+{
+       acpi_status rc;
+       struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+       struct acpi_object_list input = {
+               .count = 2,
+               .pointer = (union acpi_object []) {
+                       [0] = {
+                               .integer.type = ACPI_TYPE_INTEGER,
+                               .integer.value = offset,
+                       },
+                       [1] = {
+                               .integer.type = ACPI_TYPE_INTEGER,
+                               .integer.value = len,
+                       },
+               },
+       };
+
+       rc = acpi_evaluate_object(handle, "_LSR", &input, &buf);
+       if (ACPI_FAILURE(rc))
+               return NULL;
+       return pkg_to_buf(buf.pointer);
+}
+
+static union acpi_object *acpi_label_info(acpi_handle handle)
+{
+       acpi_status rc;
+       struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+
+       rc = acpi_evaluate_object(handle, "_LSI", NULL, &buf);
+       if (ACPI_FAILURE(rc))
+               return NULL;
+       return pkg_to_buf(buf.pointer);
 }
 
 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);
+       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;
        }
 
        if (nvdimm) {
-               struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
                struct acpi_device *adev = nfit_mem->adev;
 
                if (!adev)
                        in_buf.buffer.pointer,
                        min_t(u32, 256, in_buf.buffer.length), true);
 
-       out_obj = acpi_evaluate_dsm(handle, guid, 1, func, &in_obj);
+       /* call the BIOS, prefer the named methods over _DSM if available */
+       if (cmd == ND_CMD_GET_CONFIG_SIZE && nfit_mem->has_lsi)
+               out_obj = acpi_label_info(handle);
+       else if (cmd == ND_CMD_GET_CONFIG_DATA && nfit_mem->has_lsr) {
+               struct nd_cmd_get_config_data_hdr *p = buf;
+
+               out_obj = acpi_label_read(handle, p->in_offset, p->in_length);
+       } else if (cmd == ND_CMD_SET_CONFIG_DATA && nfit_mem->has_lsw) {
+               struct nd_cmd_set_config_hdr *p = buf;
+
+               out_obj = acpi_label_write(handle, p->in_offset, p->in_length,
+                               p->in_buf);
+       } else
+               out_obj = acpi_evaluate_dsm(handle, guid, 1, func, &in_obj);
+
        if (!out_obj) {
                dev_dbg(dev, "%s:%s _DSM failed cmd: %s\n", __func__, dimm_name,
                                cmd_name);
 {
        struct acpi_device *adev, *adev_dimm;
        struct device *dev = acpi_desc->dev;
+       union acpi_object *obj;
        unsigned long dsm_mask;
        const guid_t *guid;
        int i;
                if (acpi_check_dsm(adev_dimm->handle, guid, 1, 1ULL << i))
                        set_bit(i, &nfit_mem->dsm_mask);
 
+       obj = acpi_label_info(adev_dimm->handle);
+       if (obj) {
+               ACPI_FREE(obj);
+               nfit_mem->has_lsi = 1;
+               dev_dbg(dev, "%s: has _LSI\n", dev_name(&adev_dimm->dev));
+       }
+
+       obj = acpi_label_read(adev_dimm->handle, 0, 0);
+       if (obj) {
+               ACPI_FREE(obj);
+               nfit_mem->has_lsr = 1;
+               dev_dbg(dev, "%s: has _LSR\n", dev_name(&adev_dimm->dev));
+       }
+
+       obj = acpi_label_write(adev_dimm->handle, 0, 0, NULL);
+       if (obj) {
+               ACPI_FREE(obj);
+               nfit_mem->has_lsw = 1;
+               dev_dbg(dev, "%s: has _LSW\n", dev_name(&adev_dimm->dev));
+       }
+
        return 0;
 }
 
                if (nfit_mem->family == NVDIMM_FAMILY_INTEL)
                        cmd_mask |= nfit_mem->dsm_mask;
 
+               if (nfit_mem->has_lsi)
+                       set_bit(ND_CMD_GET_CONFIG_SIZE, &cmd_mask);
+               if (nfit_mem->has_lsr)
+                       set_bit(ND_CMD_GET_CONFIG_DATA, &cmd_mask);
+               if (nfit_mem->has_lsw)
+                       set_bit(ND_CMD_SET_CONFIG_DATA, &cmd_mask);
+
                flush = nfit_mem->nfit_flush ? nfit_mem->nfit_flush->flush
                        : NULL;
                nvdimm = nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,