freebsd-skq/sys/dev/nvdimm/nvdimm_e820.c
cem 8980f3ad9a nvdimm_e820: Fix braino in size=all SPA hint
The sentinel value for "use the rest of the region," -1, isn't zero modulo
PAGE_SIZE.  Relax the check to permit the intended special value.

X-MFC-With:	r353110
Sponsored by:	Dell EMC Isilon
2019-10-18 03:01:21 +00:00

395 lines
10 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Dell EMC Isilon
*
* 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/bitstring.h>
#include <sys/bus.h>
#include <sys/efi.h>
#include <sys/kernel.h>
#include <sys/linker.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sbuf.h>
#include <sys/uuid.h>
#include <vm/vm_param.h>
#include <machine/metadata.h>
#include <machine/pc/bios.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/nvdimm/nvdimm_var.h>
struct nvdimm_e820_bus {
SLIST_HEAD(, SPA_mapping) spas;
};
#define NVDIMM_E820 "nvdimm_e820"
static MALLOC_DEFINE(M_NVDIMM_E820, NVDIMM_E820, "NVDIMM e820 bus memory");
static const struct bios_smap *smapbase;
static struct {
vm_paddr_t start;
vm_paddr_t size;
} pram_segments[VM_PHYSSEG_MAX];
static unsigned pram_nreg;
static void
nvdimm_e820_dump_prams(device_t dev, const char *func, int hintunit)
{
char buffer[256];
struct sbuf sb;
bool printed = false;
unsigned i;
sbuf_new(&sb, buffer, sizeof(buffer), SBUF_FIXEDLEN);
sbuf_set_drain(&sb, sbuf_printf_drain, NULL);
sbuf_printf(&sb, "%s: %s: ", device_get_nameunit(dev), func);
if (hintunit < 0)
sbuf_cat(&sb, "Found BIOS PRAM regions: ");
else
sbuf_printf(&sb, "Remaining unallocated PRAM regions after "
"hint %d: ", hintunit);
for (i = 0; i < pram_nreg; i++) {
if (pram_segments[i].size == 0)
continue;
if (printed)
sbuf_putc(&sb, ',');
else
printed = true;
sbuf_printf(&sb, "0x%jx-0x%jx",
(uintmax_t)pram_segments[i].start,
(uintmax_t)pram_segments[i].start + pram_segments[i].size
- 1);
}
if (!printed)
sbuf_cat(&sb, "<none>");
sbuf_putc(&sb, '\n');
sbuf_finish(&sb);
sbuf_delete(&sb);
}
static int
nvdimm_e820_create_spas(device_t dev)
{
static const vm_size_t HINT_ALL = (vm_size_t)-1;
ACPI_NFIT_SYSTEM_ADDRESS nfit_sa;
struct SPA_mapping *spa_mapping;
enum SPA_mapping_type spa_type;
struct nvdimm_e820_bus *sc;
const char *hinttype;
long hintaddrl, hintsizel;
vm_paddr_t hintaddr;
vm_size_t hintsize;
unsigned i, j;
int error;
sc = device_get_softc(dev);
error = 0;
nfit_sa = (ACPI_NFIT_SYSTEM_ADDRESS) { 0 };
if (bootverbose)
nvdimm_e820_dump_prams(dev, __func__, -1);
for (i = 0;
resource_long_value("nvdimm_spa", i, "maddr", &hintaddrl) == 0;
i++) {
if (resource_long_value("nvdimm_spa", i, "msize", &hintsizel)
!= 0) {
device_printf(dev, "hint.nvdimm_spa.%u missing msize\n",
i);
continue;
}
hintaddr = (vm_paddr_t)hintaddrl;
hintsize = (vm_size_t)hintsizel;
if ((hintaddr & PAGE_MASK) != 0 ||
((hintsize & PAGE_MASK) != 0 && hintsize != HINT_ALL)) {
device_printf(dev, "hint.nvdimm_spa.%u addr or size "
"not page aligned\n", i);
continue;
}
if (resource_string_value("nvdimm_spa", i, "type", &hinttype)
!= 0) {
device_printf(dev, "hint.nvdimm_spa.%u missing type\n",
i);
continue;
}
spa_type = nvdimm_spa_type_from_name(hinttype);
if (spa_type == SPA_TYPE_UNKNOWN) {
device_printf(dev, "hint.nvdimm_spa%u.type does not "
"match any known SPA types\n", i);
continue;
}
for (j = 0; j < pram_nreg; j++) {
if (pram_segments[j].start <= hintaddr &&
(hintsize == HINT_ALL ||
(pram_segments[j].start + pram_segments[j].size) >=
(hintaddr + hintsize)))
break;
}
if (j == pram_nreg) {
device_printf(dev, "hint.nvdimm_spa%u hint does not "
"match any region\n", i);
continue;
}
/* Carve off "SPA" from available regions. */
if (pram_segments[j].start == hintaddr) {
/* Easy case first: beginning of segment. */
if (hintsize == HINT_ALL)
hintsize = pram_segments[j].size;
pram_segments[j].start += hintsize;
pram_segments[j].size -= hintsize;
/* We might leave an empty segment; who cares. */
} else if (hintsize == HINT_ALL ||
(pram_segments[j].start + pram_segments[j].size) ==
(hintaddr + hintsize)) {
/* 2nd easy case: end of segment. */
if (hintsize == HINT_ALL)
hintsize = pram_segments[j].size -
(hintaddr - pram_segments[j].start);
pram_segments[j].size -= hintsize;
} else {
/* Hard case: mid segment. */
if (pram_nreg == nitems(pram_segments)) {
/* Improbable, but handle gracefully. */
device_printf(dev, "Ran out of %zu segments\n",
nitems(pram_segments));
error = ENOBUFS;
break;
}
if (j != pram_nreg - 1) {
memmove(&pram_segments[j + 2],
&pram_segments[j + 1],
(pram_nreg - 1 - j) *
sizeof(pram_segments[0]));
}
pram_nreg++;
pram_segments[j + 1].start = hintaddr + hintsize;
pram_segments[j + 1].size =
(pram_segments[j].start + pram_segments[j].size) -
(hintaddr + hintsize);
pram_segments[j].size = hintaddr -
pram_segments[j].start;
}
if (bootverbose)
nvdimm_e820_dump_prams(dev, __func__, (int)i);
spa_mapping = malloc(sizeof(*spa_mapping), M_NVDIMM_E820,
M_WAITOK | M_ZERO);
/* Mock up a super primitive table for nvdimm_spa_init(). */
nfit_sa.RangeIndex = i;
nfit_sa.Flags = 0;
nfit_sa.Address = hintaddr;
nfit_sa.Length = hintsize;
nfit_sa.MemoryMapping = EFI_MD_ATTR_WB | EFI_MD_ATTR_WT |
EFI_MD_ATTR_UC;
error = nvdimm_spa_init(spa_mapping, &nfit_sa, spa_type);
if (error != 0) {
nvdimm_spa_fini(spa_mapping);
free(spa_mapping, M_NVDIMM_E820);
break;
}
SLIST_INSERT_HEAD(&sc->spas, spa_mapping, link);
}
return (error);
}
static int
nvdimm_e820_remove_spas(device_t dev)
{
struct nvdimm_e820_bus *sc;
struct SPA_mapping *spa, *next;
sc = device_get_softc(dev);
SLIST_FOREACH_SAFE(spa, &sc->spas, link, next) {
nvdimm_spa_fini(spa);
SLIST_REMOVE_HEAD(&sc->spas, link);
free(spa, M_NVDIMM_E820);
}
return (0);
}
static void
nvdimm_e820_identify(driver_t *driver __unused, device_t parent)
{
device_t child;
caddr_t kmdp;
if (resource_disabled(NVDIMM_E820, 0))
return;
/* Just create a single instance of the fake bus. */
if (device_find_child(parent, NVDIMM_E820, -1) != NULL)
return;
kmdp = preload_search_by_type("elf kernel");
if (kmdp == NULL)
kmdp = preload_search_by_type("elf64 kernel");
smapbase = (const void *)preload_search_info(kmdp,
MODINFO_METADATA | MODINFOMD_SMAP);
/* Only supports BIOS SMAP for now. */
if (smapbase == NULL)
return;
child = BUS_ADD_CHILD(parent, 0, NVDIMM_E820, -1);
if (child == NULL)
device_printf(parent, "add %s child failed\n", NVDIMM_E820);
}
static int
nvdimm_e820_probe(device_t dev)
{
/*
* nexus panics if a child doesn't have ivars. BUS_ADD_CHILD uses
* nexus_add_child, which creates fuckin ivars. but sometimes if you
* unload and reload nvdimm_e820, the device node stays but the ivars
* are deleted??? avoid trivial panic but this is a kludge.
*/
if (device_get_ivars(dev) == NULL)
return (ENXIO);
device_quiet(dev);
device_set_desc(dev, "Legacy e820 NVDIMM root device");
return (BUS_PROBE_NOWILDCARD);
}
static int
nvdimm_e820_attach(device_t dev)
{
const struct bios_smap *smapend, *smap;
uint32_t smapsize;
unsigned nregions;
int error;
smapsize = *((const uint32_t *)smapbase - 1);
smapend = (const void *)((const char *)smapbase + smapsize);
for (nregions = 0, smap = smapbase; smap < smapend; smap++) {
if (smap->type != SMAP_TYPE_PRAM || smap->length == 0)
continue;
pram_segments[nregions].start = smap->base;
pram_segments[nregions].size = smap->length;
device_printf(dev, "Found PRAM 0x%jx +0x%jx\n",
(uintmax_t)smap->base, (uintmax_t)smap->length);
nregions++;
}
if (nregions == 0) {
device_printf(dev, "No e820 PRAM regions detected\n");
return (ENXIO);
}
pram_nreg = nregions;
error = nvdimm_e820_create_spas(dev);
return (error);
}
static int
nvdimm_e820_detach(device_t dev)
{
int error;
error = nvdimm_e820_remove_spas(dev);
return (error);
}
static device_method_t nvdimm_e820_methods[] = {
DEVMETHOD(device_identify, nvdimm_e820_identify),
DEVMETHOD(device_probe, nvdimm_e820_probe),
DEVMETHOD(device_attach, nvdimm_e820_attach),
DEVMETHOD(device_detach, nvdimm_e820_detach),
DEVMETHOD_END
};
static driver_t nvdimm_e820_driver = {
NVDIMM_E820,
nvdimm_e820_methods,
sizeof(struct nvdimm_e820_bus),
};
static devclass_t nvdimm_e820_devclass;
static int
nvdimm_e820_chainevh(struct module *m, int e, void *arg __unused)
{
devclass_t dc;
device_t dev, parent;
int i, error, maxunit;
switch (e) {
case MOD_UNLOAD:
dc = nvdimm_e820_devclass;
maxunit = devclass_get_maxunit(dc);
for (i = 0; i < maxunit; i++) {
dev = devclass_get_device(dc, i);
if (dev == NULL)
continue;
parent = device_get_parent(dev);
if (parent == NULL) {
/* Not sure how this would happen. */
continue;
}
error = device_delete_child(parent, dev);
if (error != 0)
return (error);
}
break;
default:
/* Prevent compiler warning about unhandled cases. */
break;
}
return (0);
}
DRIVER_MODULE(nvdimm_e820, nexus, nvdimm_e820_driver, nvdimm_e820_devclass,
nvdimm_e820_chainevh, NULL);