nvdimm: only enumerate present nvdimm devices

Not all child devices of the NVDIMM root device represent DIMM devices
which are present in the system. The spec says (ACPI 6.2, sec 9.20.2):

    For each NVDIMM present or intended to be supported by platform,
    platform firmware also exposes an NVDIMM device ... under the
    NVDIMM root device.

Present NVDIMM devices are found by walking all of the NFIT table's
SPA ranges, then walking the NVDIMM regions mentioned by those SPA
ranges.

A set of NFIT walking helper functions are introduced to avoid the
need to splat the enumeration logic across several disparate
callbacks.

Submitted by:	D Scott Phillips <d.scott.phillips@intel.com>
Sponsored by:	Intel Corporation
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D18439
This commit is contained in:
Konstantin Belousov 2019-01-31 22:47:04 +00:00
parent 7dcbca8d67
commit 7674dce0a4
4 changed files with 305 additions and 151 deletions

View file

@ -76,99 +76,6 @@ nvdimm_find_by_handle(nfit_handle_t nv_handle)
return (res);
}
static int
nvdimm_parse_flush_addr(void *nfitsubtbl, void *arg)
{
ACPI_NFIT_FLUSH_ADDRESS *nfitflshaddr;
struct nvdimm_dev *nv;
int i;
nfitflshaddr = nfitsubtbl;
nv = arg;
if (nfitflshaddr->DeviceHandle != nv->nv_handle)
return (0);
MPASS(nv->nv_flush_addr == NULL && nv->nv_flush_addr_cnt == 0);
nv->nv_flush_addr = mallocarray(nfitflshaddr->HintCount,
sizeof(uint64_t *), M_NVDIMM, M_WAITOK);
for (i = 0; i < nfitflshaddr->HintCount; i++)
nv->nv_flush_addr[i] = (uint64_t *)nfitflshaddr->HintAddress[i];
nv->nv_flush_addr_cnt = nfitflshaddr->HintCount;
return (0);
}
int
nvdimm_iterate_nfit(ACPI_TABLE_NFIT *nfitbl, enum AcpiNfitType type,
int (*cb)(void *, void *), void *arg)
{
ACPI_NFIT_HEADER *nfithdr;
ACPI_NFIT_SYSTEM_ADDRESS *nfitaddr;
ACPI_NFIT_MEMORY_MAP *nfitmap;
ACPI_NFIT_INTERLEAVE *nfitintrl;
ACPI_NFIT_SMBIOS *nfitsmbios;
ACPI_NFIT_CONTROL_REGION *nfitctlreg;
ACPI_NFIT_DATA_REGION *nfitdtreg;
ACPI_NFIT_FLUSH_ADDRESS *nfitflshaddr;
char *ptr;
int error;
error = 0;
for (ptr = (char *)(nfitbl + 1);
ptr < (char *)nfitbl + nfitbl->Header.Length;
ptr += nfithdr->Length) {
nfithdr = (ACPI_NFIT_HEADER *)ptr;
if (nfithdr->Type != type)
continue;
switch (nfithdr->Type) {
case ACPI_NFIT_TYPE_SYSTEM_ADDRESS:
nfitaddr = __containerof(nfithdr,
ACPI_NFIT_SYSTEM_ADDRESS, Header);
error = cb(nfitaddr, arg);
break;
case ACPI_NFIT_TYPE_MEMORY_MAP:
nfitmap = __containerof(nfithdr,
ACPI_NFIT_MEMORY_MAP, Header);
error = cb(nfitmap, arg);
break;
case ACPI_NFIT_TYPE_INTERLEAVE:
nfitintrl = __containerof(nfithdr,
ACPI_NFIT_INTERLEAVE, Header);
error = cb(nfitintrl, arg);
break;
case ACPI_NFIT_TYPE_SMBIOS:
nfitsmbios = __containerof(nfithdr,
ACPI_NFIT_SMBIOS, Header);
error = cb(nfitsmbios, arg);
break;
case ACPI_NFIT_TYPE_CONTROL_REGION:
nfitctlreg = __containerof(nfithdr,
ACPI_NFIT_CONTROL_REGION, Header);
error = cb(nfitctlreg, arg);
break;
case ACPI_NFIT_TYPE_DATA_REGION:
nfitdtreg = __containerof(nfithdr,
ACPI_NFIT_DATA_REGION, Header);
error = cb(nfitdtreg, arg);
break;
case ACPI_NFIT_TYPE_FLUSH_ADDRESS:
nfitflshaddr = __containerof(nfithdr,
ACPI_NFIT_FLUSH_ADDRESS, Header);
error = cb(nfitflshaddr, arg);
break;
case ACPI_NFIT_TYPE_RESERVED:
default:
if (bootverbose)
printf("NFIT subtype %d unknown\n",
nfithdr->Type);
error = 0;
break;
}
if (error != 0)
break;
}
return (error);
}
static int
nvdimm_probe(device_t dev)
{
@ -197,8 +104,8 @@ nvdimm_attach(device_t dev)
device_printf(dev, "cannot get NFIT\n");
return (ENXIO);
}
nvdimm_iterate_nfit(nfitbl, ACPI_NFIT_TYPE_FLUSH_ADDRESS,
nvdimm_parse_flush_addr, nv);
acpi_nfit_get_flush_addrs(nfitbl, nv->nv_handle, &nv->nv_flush_addr,
&nv->nv_flush_addr_cnt);
AcpiPutTable(&nfitbl->Header);
return (0);
}
@ -227,57 +134,92 @@ nvdimm_resume(device_t dev)
return (0);
}
static int
nvdimm_root_create_spa(void *nfitsubtbl, void *arg)
static ACPI_STATUS
find_dimm(ACPI_HANDLE handle, UINT32 nesting_level, void *context,
void **return_value)
{
enum SPA_mapping_type spa_type;
ACPI_NFIT_SYSTEM_ADDRESS *nfitaddr;
struct SPA_mapping *spa;
struct nvdimm_root_dev *dev;
int error;
ACPI_DEVICE_INFO *device_info;
ACPI_STATUS status;
nfitaddr = nfitsubtbl;
dev = arg;
spa_type = nvdimm_spa_type_from_uuid(
(struct uuid *)nfitaddr->RangeGuid);
if (spa_type == SPA_TYPE_UNKNOWN)
return (0);
spa = malloc(sizeof(struct SPA_mapping), M_NVDIMM, M_WAITOK | M_ZERO);
error = nvdimm_spa_init(spa, nfitaddr, spa_type);
if (error != 0) {
nvdimm_spa_fini(spa);
free(spa, M_NVDIMM);
status = AcpiGetObjectInfo(handle, &device_info);
if (ACPI_FAILURE(status))
return_ACPI_STATUS(AE_ERROR);
if (device_info->Address == (uintptr_t)context) {
*(ACPI_HANDLE *)return_value = handle;
return_ACPI_STATUS(AE_CTRL_TERMINATE);
}
SLIST_INSERT_HEAD(&dev->spas, spa, link);
return_ACPI_STATUS(AE_OK);
}
static ACPI_HANDLE
get_dimm_acpi_handle(ACPI_HANDLE root_handle, nfit_handle_t adr)
{
ACPI_HANDLE res;
ACPI_STATUS status;
res = NULL;
status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, root_handle, 1, find_dimm,
NULL, (void *)(uintptr_t)adr, &res);
if (ACPI_FAILURE(status))
res = NULL;
return (res);
}
static int
nvdimm_root_create_devs(device_t dev, ACPI_TABLE_NFIT *nfitbl)
{
ACPI_HANDLE root_handle, dimm_handle;
device_t child;
nfit_handle_t *dimm_ids, *dimm;
uintptr_t *ivars;
int num_dimm_ids;
root_handle = acpi_get_handle(dev);
acpi_nfit_get_dimm_ids(nfitbl, &dimm_ids, &num_dimm_ids);
for (dimm = dimm_ids; dimm < dimm_ids + num_dimm_ids; dimm++) {
dimm_handle = get_dimm_acpi_handle(root_handle, *dimm);
child = BUS_ADD_CHILD(dev, 100, "nvdimm", -1);
if (child == NULL) {
device_printf(dev, "failed to create nvdimm\n");
return (ENXIO);
}
ivars = mallocarray(NVDIMM_ROOT_IVAR_MAX, sizeof(uintptr_t),
M_NVDIMM, M_ZERO | M_WAITOK);
device_set_ivars(child, ivars);
nvdimm_root_set_acpi_handle(child, dimm_handle);
nvdimm_root_set_device_handle(child, *dimm);
}
free(dimm_ids, M_NVDIMM);
return (0);
}
static ACPI_STATUS
nvdimm_root_create_dev(ACPI_HANDLE handle, UINT32 nesting_level, void *context,
void **return_value)
static int
nvdimm_root_create_spas(struct nvdimm_root_dev *dev, ACPI_TABLE_NFIT *nfitbl)
{
ACPI_STATUS status;
ACPI_DEVICE_INFO *device_info;
device_t parent, child;
uintptr_t *ivars;
ACPI_NFIT_SYSTEM_ADDRESS **spas, **spa;
struct SPA_mapping *spa_mapping;
enum SPA_mapping_type spa_type;
int error, num_spas;
parent = context;
child = BUS_ADD_CHILD(parent, 100, "nvdimm", -1);
if (child == NULL) {
device_printf(parent, "failed to create nvdimm\n");
return_ACPI_STATUS(AE_ERROR);
error = 0;
acpi_nfit_get_spa_ranges(nfitbl, &spas, &num_spas);
for (spa = spas; spa < spas + num_spas; spa++) {
spa_type = nvdimm_spa_type_from_uuid(
(struct uuid *)(*spa)->RangeGuid);
if (spa_type == SPA_TYPE_UNKNOWN)
continue;
spa_mapping = malloc(sizeof(struct SPA_mapping), M_NVDIMM,
M_WAITOK | M_ZERO);
error = nvdimm_spa_init(spa_mapping, *spa, spa_type);
if (error != 0) {
nvdimm_spa_fini(spa_mapping);
free(spa, M_NVDIMM);
break;
}
SLIST_INSERT_HEAD(&dev->spas, spa_mapping, link);
}
status = AcpiGetObjectInfo(handle, &device_info);
if (ACPI_FAILURE(status)) {
device_printf(parent, "failed to get nvdimm device info\n");
return_ACPI_STATUS(AE_ERROR);
}
ivars = mallocarray(NVDIMM_ROOT_IVAR_MAX - 1, sizeof(uintptr_t),
M_NVDIMM, M_ZERO | M_WAITOK);
device_set_ivars(child, ivars);
nvdimm_root_set_acpi_handle(child, handle);
nvdimm_root_set_device_handle(child, device_info->Address);
return_ACPI_STATUS(AE_OK);
free(spas, M_NVDIMM);
return (error);
}
static char *nvdimm_root_id[] = {"ACPI0012", NULL};
@ -299,26 +241,24 @@ nvdimm_root_probe(device_t dev)
static int
nvdimm_root_attach(device_t dev)
{
ACPI_HANDLE handle;
ACPI_STATUS status;
struct nvdimm_root_dev *root;
ACPI_TABLE_NFIT *nfitbl;
ACPI_STATUS status;
int error;
handle = acpi_get_handle(dev);
status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1,
nvdimm_root_create_dev, NULL, dev, NULL);
if (ACPI_FAILURE(status))
device_printf(dev, "failed adding children\n");
error = bus_generic_attach(dev);
if (error != 0)
return (error);
status = AcpiGetTable(ACPI_SIG_NFIT, 1, (ACPI_TABLE_HEADER **)&nfitbl);
if (ACPI_FAILURE(status)) {
device_printf(dev, "cannot get NFIT\n");
return (ENXIO);
}
error = nvdimm_iterate_nfit(nfitbl, ACPI_NFIT_TYPE_SYSTEM_ADDRESS,
nvdimm_root_create_spa, device_get_softc(dev));
error = nvdimm_root_create_devs(dev, nfitbl);
if (error != 0)
return (error);
error = bus_generic_attach(dev);
if (error != 0)
return (error);
root = device_get_softc(dev);
error = nvdimm_root_create_spas(root, nfitbl);
AcpiPutTable(&nfitbl->Header);
return (error);
}

View file

@ -0,0 +1,203 @@
/*-
* Copyright (c) 2018 Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bio.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/uuid.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#include <dev/nvdimm/nvdimm_var.h>
static int
uint32_t_compare(const void *a, const void *b)
{
return (*(const uint32_t *)a - *(const uint32_t *)b);
}
static int
find_matches(ACPI_TABLE_NFIT *nfitbl, uint16_t type, uint16_t offset,
uint64_t mask, uint64_t value, void **ptrs, int ptrs_len)
{
ACPI_NFIT_HEADER *h, *end;
uint64_t val;
size_t load_size;
int count;
h = (ACPI_NFIT_HEADER *)(nfitbl + 1);
end = (ACPI_NFIT_HEADER *)((char *)nfitbl +
nfitbl->Header.Length);
load_size = roundup2(flsl(mask), 8) / 8;
count = 0;
while (h < end) {
if (h->Type == type) {
bcopy((char *)h + offset, &val, load_size);
val &= mask;
if (val == value) {
if (ptrs_len > 0) {
ptrs[count] = h;
ptrs_len--;
}
count++;
}
}
if (h->Length == 0)
break;
h = (ACPI_NFIT_HEADER *)((char *)h + h->Length);
}
return (count);
}
static void
malloc_find_matches(ACPI_TABLE_NFIT *nfitbl, uint16_t type, uint16_t offset,
uint64_t mask, uint64_t value, void ***ptrs, int *ptrs_len)
{
int count;
count = find_matches(nfitbl, type, offset, mask, value, NULL, 0);
*ptrs_len = count;
if (count == 0) {
*ptrs = NULL;
return;
}
*ptrs = mallocarray(count, sizeof(void *), M_NVDIMM, M_WAITOK);
find_matches(nfitbl, type, offset, mask, value, *ptrs, *ptrs_len);
}
void
acpi_nfit_get_dimm_ids(ACPI_TABLE_NFIT *nfitbl, nfit_handle_t **listp,
int *countp)
{
ACPI_NFIT_SYSTEM_ADDRESS **spas;
ACPI_NFIT_MEMORY_MAP ***regions;
int i, j, k, maxids, num_spas, *region_counts;
acpi_nfit_get_spa_ranges(nfitbl, &spas, &num_spas);
if (num_spas == 0) {
*listp = NULL;
*countp = 0;
return;
}
regions = mallocarray(num_spas, sizeof(uint16_t *), M_NVDIMM,
M_WAITOK);
region_counts = mallocarray(num_spas, sizeof(int), M_NVDIMM, M_WAITOK);
for (i = 0; i < num_spas; i++) {
acpi_nfit_get_region_mappings_by_spa_range(nfitbl,
spas[i]->RangeIndex, &regions[i], &region_counts[i]);
}
maxids = 0;
for (i = 0; i < num_spas; i++) {
maxids += region_counts[i];
}
*listp = mallocarray(maxids, sizeof(nfit_handle_t), M_NVDIMM, M_WAITOK);
k = 0;
for (i = 0; i < num_spas; i++) {
for (j = 0; j < region_counts[i]; j++)
(*listp)[k++] = regions[i][j]->DeviceHandle;
}
qsort((*listp), maxids, sizeof(uint32_t), uint32_t_compare);
i = 0;
for (j = 1; j < maxids; j++) {
if ((*listp)[i] != (*listp)[j])
(*listp)[++i] = (*listp)[j];
}
*countp = i + 1;
free(region_counts, M_NVDIMM);
for (i = 0; i < num_spas; i++)
free(regions[i], M_NVDIMM);
free(regions, M_NVDIMM);
free(spas, M_NVDIMM);
}
void
acpi_nfit_get_spa_range(ACPI_TABLE_NFIT *nfitbl, uint16_t range_index,
ACPI_NFIT_SYSTEM_ADDRESS **spa)
{
*spa = NULL;
find_matches(nfitbl, ACPI_NFIT_TYPE_SYSTEM_ADDRESS,
offsetof(ACPI_NFIT_SYSTEM_ADDRESS, RangeIndex), UINT16_MAX,
range_index, (void **)spa, 1);
}
void
acpi_nfit_get_spa_ranges(ACPI_TABLE_NFIT *nfitbl,
ACPI_NFIT_SYSTEM_ADDRESS ***listp, int *countp)
{
malloc_find_matches(nfitbl, ACPI_NFIT_TYPE_SYSTEM_ADDRESS, 0, 0, 0,
(void ***)listp, countp);
}
void
acpi_nfit_get_region_mappings_by_spa_range(ACPI_TABLE_NFIT *nfitbl,
uint16_t spa_range_index, ACPI_NFIT_MEMORY_MAP ***listp, int *countp)
{
malloc_find_matches(nfitbl, ACPI_NFIT_TYPE_MEMORY_MAP,
offsetof(ACPI_NFIT_MEMORY_MAP, RangeIndex), UINT16_MAX,
spa_range_index, (void ***)listp, countp);
}
void acpi_nfit_get_control_region(ACPI_TABLE_NFIT *nfitbl,
uint16_t control_region_index, ACPI_NFIT_CONTROL_REGION **out)
{
*out = NULL;
find_matches(nfitbl, ACPI_NFIT_TYPE_CONTROL_REGION,
offsetof(ACPI_NFIT_CONTROL_REGION, RegionIndex), UINT16_MAX,
control_region_index, (void **)out, 1);
}
void
acpi_nfit_get_flush_addrs(ACPI_TABLE_NFIT *nfitbl, nfit_handle_t dimm,
uint64_t ***listp, int *countp)
{
ACPI_NFIT_FLUSH_ADDRESS *subtable;
int i;
subtable = NULL;
find_matches(nfitbl, ACPI_NFIT_TYPE_FLUSH_ADDRESS,
offsetof(ACPI_NFIT_FLUSH_ADDRESS, DeviceHandle), UINT32_MAX,
dimm, (void **)&subtable, 1);
if (subtable == NULL || subtable->HintCount == 0) {
*listp = NULL;
*countp = 0;
return;
}
*countp = subtable->HintCount;
*listp = mallocarray(subtable->HintCount, sizeof(uint64_t *), M_NVDIMM,
M_WAITOK);
for (i = 0; i < subtable->HintCount; i++)
(*listp)[i] = (uint64_t *)(intptr_t)subtable->HintAddress[i];
}

View file

@ -91,10 +91,20 @@ struct SPA_mapping {
MALLOC_DECLARE(M_NVDIMM);
void acpi_nfit_get_dimm_ids(ACPI_TABLE_NFIT *nfitbl, nfit_handle_t **listp,
int *countp);
void acpi_nfit_get_spa_range(ACPI_TABLE_NFIT *nfitbl, uint16_t range_index,
ACPI_NFIT_SYSTEM_ADDRESS **spa);
void acpi_nfit_get_spa_ranges(ACPI_TABLE_NFIT *nfitbl,
ACPI_NFIT_SYSTEM_ADDRESS ***listp, int *countp);
void acpi_nfit_get_region_mappings_by_spa_range(ACPI_TABLE_NFIT *nfitbl,
uint16_t spa_range_index, ACPI_NFIT_MEMORY_MAP ***listp, int *countp);
void acpi_nfit_get_control_region(ACPI_TABLE_NFIT *nfitbl,
uint16_t control_region_index, ACPI_NFIT_CONTROL_REGION **out);
void acpi_nfit_get_flush_addrs(ACPI_TABLE_NFIT *nfitbl, nfit_handle_t dimm,
uint64_t ***listp, int *countp);
enum SPA_mapping_type nvdimm_spa_type_from_uuid(struct uuid *);
struct nvdimm_dev *nvdimm_find_by_handle(nfit_handle_t nv_handle);
int nvdimm_iterate_nfit(ACPI_TABLE_NFIT *nfitbl, enum AcpiNfitType type,
int (*cb)(void *, void *), void *arg);
int nvdimm_spa_init(struct SPA_mapping *spa, ACPI_NFIT_SYSTEM_ADDRESS *nfitaddr,
enum SPA_mapping_type spa_type);
void nvdimm_spa_fini(struct SPA_mapping *spa);

View file

@ -4,6 +4,7 @@
KMOD= nvdimm
SRCS= nvdimm.c \
nvdimm_nfit.c \
nvdimm_spa.c
SRCS+= acpi_if.h bus_if.h device_if.h