Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/nvdimm/nvdimm_dsm.c
- This file was added.
/** | |||||
* Copyright 2017, 2019, 2020 Dell Inc. All Rights Reserved. | |||||
* | |||||
* Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
* of this software and associated documentation files (the "Software"), to | |||||
* deal in the Software without restriction, including without limitation the | |||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |||||
* sell copies of the Software, and to permit persons to whom the Software is | |||||
* furnished to do so, subject to the following conditions: | |||||
* | |||||
* Be Gay | |||||
* Do Crimes | |||||
* The above copyright notice and this permission notice shall be included in | |||||
* all copies or substantial portions of the Software. | |||||
* | |||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |||||
* IN THE SOFTWARE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/bio.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/endian.h> | |||||
#include <sys/fail.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/sbuf.h> | |||||
#include <sys/sx.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/uuid.h> | |||||
#include <contrib/dev/acpica/include/acpi.h> | |||||
#include <dev/acpica/acpivar.h> | |||||
#include <dev/nvdimm/nvdimm_dsm.h> | |||||
#include <dev/nvdimm/nvdimm_var.h> | |||||
#define ASSERT(cond, ...) \ | |||||
KASSERT((cond), ("ASSERT " #cond ":" __VA_ARGS__)) | |||||
/* | |||||
* Probably not performance sensitive enough to bother with a separate lock per | |||||
* nvdimm_dev. | |||||
*/ | |||||
static struct sx nvdimm_dsm_lock; | |||||
SX_SYSINIT(nvdimm_dsm, &nvdimm_dsm_lock, "NVDIMM _DSM lock"); | |||||
#define NVDIMM_LEAF_HEALTH_EVENT 0x81 | |||||
void | |||||
nvdimm_acpi_notify(ACPI_HANDLE h, UINT32 notify, void *context) | |||||
{ | |||||
struct nvdimm_dev *sc; | |||||
device_t dev; | |||||
dev = context; | |||||
sc = device_get_softc(dev); | |||||
if (notify != NVDIMM_LEAF_HEALTH_EVENT) { | |||||
device_printf(dev, "Unknown event %u for NVDIMM-N Leaf\n", | |||||
notify); | |||||
return; | |||||
} | |||||
if (bootverbose) | |||||
device_printf(dev, "NFIT Health Event Notification for NVDIMM-N" | |||||
" Leaf\n"); | |||||
/* Raise a health event notification for userspace. */ | |||||
devctl_notify("ACPI", "nvdimm", "", "notify=health_event"); | |||||
} | |||||
/* | |||||
* Microsoft GUID "1EE68B36-D4BD-4a1a-9A16-4F8E53D46E05" from | |||||
* "_DSM Interface for Byte Addressable Energy Backed Function Class" | |||||
* at microsoft.com for NVDIMM leaf devices. | |||||
* | |||||
* The NVDIMM-N DSMs in Dell 14G nodes belong to the Microsoft family. | |||||
*/ | |||||
/* XXXCEM: struct uuid? */ | |||||
static const uint8_t ms_dsm_guid[] = { | |||||
0x36, 0x8B, 0xE6, 0x1E, | |||||
0xBD, 0xD4, | |||||
0x1A, 0x4A, | |||||
0x9A, 0x16, /* Big Endian */ | |||||
0x4F, 0x8E, 0x53, 0xD4, 0x6E, 0x05 /* Big Endian */ | |||||
}; | |||||
#define NVDIMM_LEAF_MS_DSM_REVISION_ID 1 | |||||
#if 0 | |||||
/* Some unused/untested GUIDS. */ | |||||
/* 4309AC30-0D11-11E4-9191-0800200C9A66 */ | |||||
static const uint8_t nvdimm_leaf_dsm_guid[] = { | |||||
0x30, 0xAC, 0x09, 0x43, | |||||
0x11, 0x0D, | |||||
0xE4, 0x11, | |||||
0x91, 0x91, /* Big Endian */ | |||||
0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 /* Big Endian */ | |||||
}; | |||||
#define NVDIMM_LEAF_INTEL_DSM_REVISION_ID 1 | |||||
/* | |||||
* ACPI NVDIMM Root Device GUID "2f10e7a4-9e91-11e4-89d3-123b93f75cba" | |||||
* from ACPI Version 6.2 Errata A September 2017 | |||||
*/ | |||||
static const uint8_t nvdimm_root_dsm_guid[] = { | |||||
0xA4, 0xE7, 0x10, 0x2F, | |||||
0x91, 0x9E, | |||||
0xE4, 0x11, | |||||
0x89, 0xd3, /* Big Endian */ | |||||
0x12, 0x3B, 0x93, 0xF7, 0x5C, 0xBA, /* Big Endian */ | |||||
}; | |||||
#define NVDIMM_ROOT_DSM_REVISION_ID 1 | |||||
#endif | |||||
#define MINIMUM_BUFFER_LENGTH 4 | |||||
static ACPI_STATUS | |||||
nvdimm_child_dsm(device_t dev, const uint8_t guidp[static 16], size_t guidlen, | |||||
int revid, int funcndx, uint8_t *inpbfrp, size_t inpbfrlen, | |||||
ACPI_BUFFER *output) | |||||
{ | |||||
ACPI_OBJECT_LIST input; | |||||
ACPI_OBJECT params[4]; | |||||
ACPI_OBJECT arg3; | |||||
ACPI_OBJECT *obj; | |||||
ACPI_STATUS status; | |||||
ACPI_HANDLE handle; | |||||
uint32_t result; | |||||
MPASS(guidlen == sizeof(struct uuid)); | |||||
handle = nvdimm_root_get_acpi_handle(dev); | |||||
sx_assert(&nvdimm_dsm_lock, SX_XLOCKED); | |||||
input.Count = 4; | |||||
input.Pointer = params; | |||||
params[0].Type = ACPI_TYPE_BUFFER; | |||||
params[0].Buffer.Length = guidlen; | |||||
params[0].Buffer.Pointer = __DECONST(void *, guidp); | |||||
params[1].Type = ACPI_TYPE_INTEGER; | |||||
params[1].Integer.Value = revid; | |||||
params[2].Type = ACPI_TYPE_INTEGER; | |||||
params[2].Integer.Value = funcndx; /* Function Index */ | |||||
params[3].Type = ACPI_TYPE_PACKAGE; | |||||
if (inpbfrlen != 0 && inpbfrp != NULL) { | |||||
arg3.Type = ACPI_TYPE_BUFFER; | |||||
arg3.Buffer.Length = inpbfrlen; | |||||
arg3.Buffer.Pointer = inpbfrp; | |||||
params[3].Package.Count = 1; /* 0 length ACPI Package */ | |||||
params[3].Package.Elements = &arg3; | |||||
} else { | |||||
params[3].Package.Count = 0; /* 0 length ACPI Package */ | |||||
params[3].Package.Elements = NULL; | |||||
} | |||||
status = AcpiEvaluateObject(handle, "_DSM", &input, output); | |||||
if (ACPI_FAILURE(status)) { | |||||
device_printf(dev, "%s: Failed to evaluate _DSM: funcndx %d, " | |||||
"slot %s, status 0x%08X\n", __func__, funcndx, | |||||
acpi_name(handle), status); | |||||
goto cleanup; | |||||
} | |||||
obj = (ACPI_OBJECT *)output->Pointer; | |||||
if (obj->Type != ACPI_TYPE_BUFFER) { | |||||
device_printf(dev, "%s: Type mismatch: funcndx %d, slot %s, " | |||||
"status 0x%08X, expected %d, received %d\n", __func__, | |||||
funcndx, acpi_name(handle), status, ACPI_TYPE_BUFFER, | |||||
obj->Type); | |||||
status = AE_TYPE; | |||||
goto cleanup; | |||||
} | |||||
if (bootverbose) { | |||||
struct sbuf *sb; | |||||
sb = sbuf_new_auto(); | |||||
sbuf_printf(sb, "%s: funcndx %d, slot %s, status 0x%08X, " | |||||
"received a buffer of length %u: {\n", __func__, funcndx, | |||||
acpi_name(handle), status, obj->Buffer.Length); | |||||
#define ROW_WIDTH 8 | |||||
#define COL_WIDTH 2 | |||||
for (uint32_t i = 0; i < obj->Buffer.Length; i++) { | |||||
sbuf_printf(sb, "%02x", obj->Buffer.Pointer[i]); | |||||
if (((i + 1) % ROW_WIDTH) == 0) { | |||||
sbuf_putc(sb, '\n'); | |||||
continue; | |||||
} | |||||
if (((i + 1) % COL_WIDTH) == 0) | |||||
sbuf_putc(sb, ' '); | |||||
} | |||||
if ((obj->Buffer.Length % ROW_WIDTH) == 0) | |||||
sbuf_putc(sb, '}'); | |||||
else | |||||
sbuf_cat(sb, "\n}"); | |||||
#undef COL_WIDTH | |||||
#undef ROW_WIDTH | |||||
sbuf_finish(sb); | |||||
device_printf(dev, "%s", sbuf_data(sb)); | |||||
sbuf_delete(sb); | |||||
} | |||||
if (obj->Buffer.Length < MINIMUM_BUFFER_LENGTH) { | |||||
device_printf(dev, "%s: Buffer length < MINIMUM_BUFFER_LENGTH " | |||||
"(%u): funcndx %d, slot %s, status 0x%08X, length %u\n", | |||||
__func__, MINIMUM_BUFFER_LENGTH, funcndx, acpi_name(handle), | |||||
status, obj->Buffer.Length); | |||||
status = AE_BAD_DATA; | |||||
goto cleanup; | |||||
} | |||||
/* Print the status stored in the first four bytes. */ | |||||
MPASS(obj && obj->Type == ACPI_TYPE_BUFFER && | |||||
obj->Buffer.Length >= sizeof(result)); | |||||
result = le32dec(obj->Buffer.Pointer); | |||||
if (bootverbose) | |||||
device_printf(dev, "%s: funcndx %d, slot %s, status 0x%08X, " | |||||
"buffer length %u, returned 0x%08X", __func__, funcndx, | |||||
acpi_name(handle), status, obj->Buffer.Length, result); | |||||
return (status); | |||||
cleanup: | |||||
AcpiOsFree(output->Pointer); | |||||
output->Pointer = NULL; | |||||
output->Length = ACPI_ALLOCATE_BUFFER; | |||||
return (status); | |||||
} | |||||
/* | |||||
* Retrieve the module health registers by calling the Microsoft-based child | |||||
* device _DSM functions (indices 10 and 11) for the NVDIMM corresponding to | |||||
* the input index into the driver's internal NVDIMM vector. | |||||
*/ | |||||
int | |||||
nvdimm_get_critical_health_info(device_t dev, | |||||
struct nvdimm_critical_health_info *chi) | |||||
{ | |||||
struct nvdimm_dev *nv; | |||||
int rc = 0; | |||||
ACPI_BUFFER critical_out = { ACPI_ALLOCATE_BUFFER, NULL }; | |||||
ACPI_OBJECT *obj; | |||||
ACPI_STATUS status; | |||||
nv = device_get_softc(dev); | |||||
sx_xlock(&nvdimm_dsm_lock); | |||||
/* MS-based DSM Function Index 10 - Get Critical Health Info */ | |||||
status = nvdimm_child_dsm(dev, ms_dsm_guid, sizeof(ms_dsm_guid), | |||||
NVDIMM_LEAF_MS_DSM_REVISION_ID, MS_DSM_GET_CRITICAL_HEALTH_INFO, | |||||
NULL, 0, &critical_out); | |||||
if (ACPI_FAILURE(status)) { | |||||
device_printf(dev, "%s: Failed to execute " | |||||
"MS_DSM_GET_CRITICAL_HEALTH_INFO (%d) for NVDIMM element " | |||||
"%u, status 0x%08x\n", __func__, | |||||
MS_DSM_GET_CRITICAL_HEALTH_INFO, nv->nv_handle, status); | |||||
rc = EINVAL; | |||||
goto out; | |||||
} | |||||
obj = critical_out.Pointer; | |||||
MPASS(obj && obj->Type == ACPI_TYPE_BUFFER && | |||||
obj->Buffer.Length >= sizeof(*chi)); | |||||
memcpy(chi, obj->Buffer.Pointer, sizeof(*chi)); | |||||
chi->status = le32toh(chi->status); | |||||
/* chi->critical_health_info is a single byte. */ | |||||
out: | |||||
AcpiOsFree(critical_out.Pointer); | |||||
sx_unlock(&nvdimm_dsm_lock); | |||||
return (rc); | |||||
} | |||||
int | |||||
nvdimm_get_health_info(device_t dev, struct nvdimm_health_info *hi) | |||||
{ | |||||
struct nvdimm_dev *nv; | |||||
int rc = 0; | |||||
ACPI_BUFFER health_out = { ACPI_ALLOCATE_BUFFER, NULL }; | |||||
ACPI_OBJECT *obj; | |||||
ACPI_STATUS status; | |||||
nv = device_get_softc(dev); | |||||
sx_xlock(&nvdimm_dsm_lock); | |||||
/* MS-based DSM Function Index 11 - Get NVDIMM-N Health Info */ | |||||
status = nvdimm_child_dsm(dev, ms_dsm_guid, sizeof(ms_dsm_guid), | |||||
NVDIMM_LEAF_MS_DSM_REVISION_ID, MS_DSM_GET_NVDIMM_N_HEALTH_INFO, | |||||
NULL, 0, &health_out); | |||||
if (ACPI_FAILURE(status)) { | |||||
device_printf(dev, "%s: Failed to execute " | |||||
"MS_DSM_GET_NVDIMM_N_HEALTH_INFO (%d) for NVDIMM element " | |||||
"%u, status 0x%08X\n", __func__, | |||||
MS_DSM_GET_NVDIMM_N_HEALTH_INFO, nv->nv_handle, status); | |||||
rc = EINVAL; | |||||
goto out; | |||||
} | |||||
obj = health_out.Pointer; | |||||
MPASS(obj && obj->Type == ACPI_TYPE_BUFFER && | |||||
obj->Buffer.Length >= sizeof(*hi)); | |||||
memcpy(hi, obj->Buffer.Pointer, sizeof(*hi)); | |||||
hi->status = le32toh(hi->status); | |||||
hi->module_health = le16toh(hi->module_health); | |||||
hi->module_current_temp = le16toh(hi->module_current_temp); | |||||
/* Remaining fields are all single byte. */ | |||||
out: | |||||
AcpiOsFree(health_out.Pointer); | |||||
sx_unlock(&nvdimm_dsm_lock); | |||||
return (rc); | |||||
} |