Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/nvdimm/nvdimm_e820.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
cem: I know I'm missing a licence header; I intend to put in the preferred 2-clause BSDL one. | |||||
* | |||||
* 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) | |||||
{ | |||||
device_printf(dev, "hint.nvdimm_spa.%u addr or size " | |||||
Not Done Inline Actions'{' should be on the prev line kib: '{' should be on the prev line | |||||
Done Inline ActionsIt runs into the 80 column threshold on the previous line. cem: It runs into the 80 column threshold on the previous line. | |||||
"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 && | |||||
Done Inline ActionsI would put {} around multi-line body. kib: I would put {} around multi-line body. | |||||
Done Inline ActionsWill do cem: Will do | |||||
(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); |
I know I'm missing a licence header; I intend to put in the preferred 2-clause BSDL one.