Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/nvdimm/nvdimm.c
/*- | /*- | ||||
* Copyright (c) 2017 The FreeBSD Foundation | * Copyright (c) 2017 The FreeBSD Foundation | ||||
* All rights reserved. | * All rights reserved. | ||||
* Copyright (c) 2018, 2019 Intel Corporation | |||||
* | * | ||||
* This software was developed by Konstantin Belousov <kib@FreeBSD.org> | * This software was developed by Konstantin Belousov <kib@FreeBSD.org> | ||||
* under sponsorship from the FreeBSD Foundation. | * under sponsorship from the FreeBSD Foundation. | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
Show All 34 Lines | |||||
#include <contrib/dev/acpica/include/acuuid.h> | #include <contrib/dev/acpica/include/acuuid.h> | ||||
#include <dev/acpica/acpivar.h> | #include <dev/acpica/acpivar.h> | ||||
#include <dev/nvdimm/nvdimm_var.h> | #include <dev/nvdimm/nvdimm_var.h> | ||||
#define _COMPONENT ACPI_OEM | #define _COMPONENT ACPI_OEM | ||||
ACPI_MODULE_NAME("NVDIMM") | ACPI_MODULE_NAME("NVDIMM") | ||||
static devclass_t nvdimm_devclass; | static devclass_t nvdimm_devclass; | ||||
static device_t *nvdimm_devs; | static devclass_t nvdimm_root_devclass; | ||||
static int nvdimm_devcnt; | |||||
MALLOC_DEFINE(M_NVDIMM, "nvdimm", "NVDIMM driver memory"); | MALLOC_DEFINE(M_NVDIMM, "nvdimm", "NVDIMM driver memory"); | ||||
struct nvdimm_dev * | struct nvdimm_dev * | ||||
nvdimm_find_by_handle(nfit_handle_t nv_handle) | nvdimm_find_by_handle(nfit_handle_t nv_handle) | ||||
{ | { | ||||
device_t dev; | struct nvdimm_dev *res; | ||||
struct nvdimm_dev *res, *nv; | device_t *dimms; | ||||
int i; | int i, error, num_dimms; | ||||
res = NULL; | res = NULL; | ||||
for (i = 0; i < nvdimm_devcnt; i++) { | error = devclass_get_devices(nvdimm_devclass, &dimms, &num_dimms); | ||||
dev = nvdimm_devs[i]; | if (error != 0) | ||||
if (dev == NULL) | return (NULL); | ||||
continue; | for (i = 0; i < num_dimms; i++) { | ||||
nv = device_get_softc(dev); | if (nvdimm_root_get_device_handle(dimms[i]) == nv_handle) { | ||||
if (nv->nv_handle == nv_handle) { | res = device_get_softc(dimms[i]); | ||||
res = nv; | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
free(dimms, M_TEMP); | |||||
return (res); | return (res); | ||||
} | } | ||||
static int | static int | ||||
nvdimm_parse_flush_addr(void *nfitsubtbl, void *arg) | nvdimm_parse_flush_addr(void *nfitsubtbl, void *arg) | ||||
{ | { | ||||
ACPI_NFIT_FLUSH_ADDRESS *nfitflshaddr; | ACPI_NFIT_FLUSH_ADDRESS *nfitflshaddr; | ||||
struct nvdimm_dev *nv; | struct nvdimm_dev *nv; | ||||
int i; | int i; | ||||
nfitflshaddr = nfitsubtbl; | nfitflshaddr = nfitsubtbl; | ||||
nv = arg; | nv = arg; | ||||
if (nfitflshaddr->DeviceHandle != nv->nv_handle) | if (nfitflshaddr->DeviceHandle != nv->nv_handle) | ||||
return (0); | return (0); | ||||
MPASS(nv->nv_flush_addr == NULL && nv->nv_flush_addr_cnt == 0); | MPASS(nv->nv_flush_addr == NULL && nv->nv_flush_addr_cnt == 0); | ||||
nv->nv_flush_addr = malloc(nfitflshaddr->HintCount * sizeof(uint64_t *), | nv->nv_flush_addr = mallocarray(nfitflshaddr->HintCount, | ||||
M_NVDIMM, M_WAITOK); | sizeof(uint64_t *), M_NVDIMM, M_WAITOK); | ||||
for (i = 0; i < nfitflshaddr->HintCount; i++) | for (i = 0; i < nfitflshaddr->HintCount; i++) | ||||
nv->nv_flush_addr[i] = (uint64_t *)nfitflshaddr->HintAddress[i]; | nv->nv_flush_addr[i] = (uint64_t *)nfitflshaddr->HintAddress[i]; | ||||
nv->nv_flush_addr_cnt = nfitflshaddr->HintCount; | nv->nv_flush_addr_cnt = nfitflshaddr->HintCount; | ||||
return (0); | return (0); | ||||
} | } | ||||
int | int | ||||
nvdimm_iterate_nfit(ACPI_TABLE_NFIT *nfitbl, enum AcpiNfitType type, | nvdimm_iterate_nfit(ACPI_TABLE_NFIT *nfitbl, enum AcpiNfitType type, | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | default: | ||||
break; | break; | ||||
} | } | ||||
if (error != 0) | if (error != 0) | ||||
break; | break; | ||||
} | } | ||||
return (error); | return (error); | ||||
} | } | ||||
static ACPI_STATUS | static int | ||||
nvdimm_walk_dev(ACPI_HANDLE handle, UINT32 level, void *ctx, void **st) | nvdimm_probe(device_t dev) | ||||
{ | { | ||||
ACPI_STATUS status; | |||||
struct nvdimm_ns_walk_ctx *wctx; | |||||
wctx = ctx; | return (BUS_PROBE_NOWILDCARD); | ||||
status = wctx->func(handle, wctx->arg); | |||||
return_ACPI_STATUS(status); | |||||
} | } | ||||
static ACPI_STATUS | static int | ||||
nvdimm_walk_root(ACPI_HANDLE handle, UINT32 level, void *ctx, void **st) | nvdimm_attach(device_t dev) | ||||
{ | { | ||||
struct nvdimm_dev *nv; | |||||
ACPI_TABLE_NFIT *nfitbl; | |||||
ACPI_HANDLE handle; | |||||
ACPI_STATUS status; | ACPI_STATUS status; | ||||
if (!acpi_MatchHid(handle, "ACPI0012")) | nv = device_get_softc(dev); | ||||
return_ACPI_STATUS(AE_OK); | handle = nvdimm_root_get_acpi_handle(dev); | ||||
status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 100, | if (handle == NULL) | ||||
nvdimm_walk_dev, NULL, ctx, NULL); | return (EINVAL); | ||||
if (ACPI_FAILURE(status)) | nv->nv_dev = dev; | ||||
return_ACPI_STATUS(status); | nv->nv_handle = nvdimm_root_get_device_handle(dev); | ||||
return_ACPI_STATUS(AE_CTRL_TERMINATE); | |||||
status = AcpiGetTable(ACPI_SIG_NFIT, 1, (ACPI_TABLE_HEADER **)&nfitbl); | |||||
if (ACPI_FAILURE(status)) { | |||||
if (bootverbose) | |||||
device_printf(dev, "cannot get NFIT\n"); | |||||
return (ENXIO); | |||||
} | } | ||||
nvdimm_iterate_nfit(nfitbl, ACPI_NFIT_TYPE_FLUSH_ADDRESS, | |||||
nvdimm_parse_flush_addr, nv); | |||||
AcpiPutTable(&nfitbl->Header); | |||||
return (0); | |||||
} | |||||
static ACPI_STATUS | static int | ||||
nvdimm_foreach_acpi(ACPI_STATUS (*func)(ACPI_HANDLE, void *), void *arg) | nvdimm_detach(device_t dev) | ||||
{ | { | ||||
struct nvdimm_ns_walk_ctx wctx; | struct nvdimm_dev *nv; | ||||
ACPI_STATUS status; | |||||
wctx.func = func; | nv = device_get_softc(dev); | ||||
wctx.arg = arg; | free(nv->nv_flush_addr, M_NVDIMM); | ||||
status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 100, | return (0); | ||||
nvdimm_walk_root, NULL, &wctx, NULL); | |||||
return_ACPI_STATUS(status); | |||||
} | } | ||||
static ACPI_STATUS | static int | ||||
nvdimm_count_devs(ACPI_HANDLE handle __unused, void *arg) | nvdimm_suspend(device_t dev) | ||||
{ | { | ||||
int *cnt; | |||||
cnt = arg; | return (0); | ||||
(*cnt)++; | |||||
ACPI_BUFFER name; | |||||
ACPI_STATUS status; | |||||
if (bootverbose) { | |||||
name.Length = ACPI_ALLOCATE_BUFFER; | |||||
status = AcpiGetName(handle, ACPI_FULL_PATHNAME, &name); | |||||
if (ACPI_FAILURE(status)) | |||||
return_ACPI_STATUS(status); | |||||
printf("nvdimm: enumerated %s\n", (char *)name.Pointer); | |||||
AcpiOsFree(name.Pointer); | |||||
} | } | ||||
return_ACPI_STATUS(AE_OK); | static int | ||||
nvdimm_resume(device_t dev) | |||||
{ | |||||
return (0); | |||||
} | } | ||||
struct nvdimm_create_dev_arg { | |||||
device_t acpi0; | |||||
int *cnt; | |||||
}; | |||||
static ACPI_STATUS | static ACPI_STATUS | ||||
nvdimm_create_dev(ACPI_HANDLE handle, void *arg) | nvdimm_root_create_dev(ACPI_HANDLE handle, UINT32 nesting_level, void *context, | ||||
void **return_value) | |||||
{ | { | ||||
struct nvdimm_create_dev_arg *narg; | ACPI_STATUS status; | ||||
device_t child; | ACPI_DEVICE_INFO *device_info; | ||||
int idx; | device_t parent, child; | ||||
uintptr_t *ivars; | |||||
narg = arg; | parent = context; | ||||
idx = *(narg->cnt); | child = BUS_ADD_CHILD(parent, 100, "nvdimm", -1); | ||||
child = device_find_child(narg->acpi0, "nvdimm", idx); | |||||
if (child == NULL) | |||||
child = BUS_ADD_CHILD(narg->acpi0, 1, "nvdimm", idx); | |||||
if (child == NULL) { | if (child == NULL) { | ||||
if (bootverbose) | device_printf(parent, "failed to create nvdimm\n"); | ||||
device_printf(narg->acpi0, | |||||
"failed to create nvdimm%d\n", idx); | |||||
return_ACPI_STATUS(AE_ERROR); | return_ACPI_STATUS(AE_ERROR); | ||||
} | } | ||||
acpi_set_handle(child, handle); | status = AcpiGetObjectInfo(handle, &device_info); | ||||
KASSERT(nvdimm_devs[idx] == NULL, ("nvdimm_devs[%d] not NULL", idx)); | |||||
nvdimm_devs[idx] = child; | |||||
(*(narg->cnt))++; | |||||
return_ACPI_STATUS(AE_OK); | |||||
} | |||||
static bool | |||||
nvdimm_init(void) | |||||
{ | |||||
ACPI_STATUS status; | |||||
if (nvdimm_devcnt != 0) | |||||
return (true); | |||||
if (acpi_disabled("nvdimm")) | |||||
return (false); | |||||
status = nvdimm_foreach_acpi(nvdimm_count_devs, &nvdimm_devcnt); | |||||
if (ACPI_FAILURE(status)) { | if (ACPI_FAILURE(status)) { | ||||
if (bootverbose) | device_printf(parent, "failed to get nvdimm device info\n"); | ||||
printf("nvdimm_init: count failed\n"); | return_ACPI_STATUS(AE_ERROR); | ||||
return (false); | |||||
} | } | ||||
nvdimm_devs = malloc(nvdimm_devcnt * sizeof(device_t), M_NVDIMM, | ivars = mallocarray(NVDIMM_ROOT_IVAR_MAX - 1, sizeof(uintptr_t), | ||||
M_WAITOK | M_ZERO); | M_NVDIMM, M_ZERO | M_WAITOK); | ||||
return (true); | 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); | |||||
} | } | ||||
static void | static char *nvdimm_root_id[] = {"ACPI0012", NULL}; | ||||
nvdimm_identify(driver_t *driver, device_t parent) | |||||
{ | |||||
struct nvdimm_create_dev_arg narg; | |||||
ACPI_STATUS status; | |||||
int i; | |||||
if (!nvdimm_init()) | |||||
return; | |||||
narg.acpi0 = parent; | |||||
narg.cnt = &i; | |||||
i = 0; | |||||
status = nvdimm_foreach_acpi(nvdimm_create_dev, &narg); | |||||
if (ACPI_FAILURE(status) && bootverbose) | |||||
printf("nvdimm_identify: create failed\n"); | |||||
} | |||||
static int | static int | ||||
nvdimm_probe(device_t dev) | nvdimm_root_probe(device_t dev) | ||||
{ | { | ||||
int rv; | |||||
return (BUS_PROBE_NOWILDCARD); | if (acpi_disabled("nvdimm")) | ||||
return (ENXIO); | |||||
rv = ACPI_ID_PROBE(device_get_parent(dev), dev, nvdimm_root_id, NULL); | |||||
if (rv <= 0) | |||||
device_set_desc(dev, "ACPI NVDIMM root device"); | |||||
return (rv); | |||||
} | } | ||||
static int | static int | ||||
nvdimm_attach(device_t dev) | nvdimm_root_attach(device_t dev) | ||||
{ | { | ||||
struct nvdimm_dev *nv; | |||||
ACPI_TABLE_NFIT *nfitbl; | |||||
ACPI_HANDLE handle; | ACPI_HANDLE handle; | ||||
ACPI_STATUS status; | ACPI_STATUS status; | ||||
int i; | int error; | ||||
nv = device_get_softc(dev); | |||||
handle = acpi_get_handle(dev); | handle = acpi_get_handle(dev); | ||||
if (handle == NULL) | status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1, | ||||
return (EINVAL); | nvdimm_root_create_dev, NULL, dev, NULL); | ||||
nv->nv_dev = dev; | if (ACPI_FAILURE(status)) | ||||
for (i = 0; i < nvdimm_devcnt; i++) { | device_printf(dev, "failed adding children\n"); | ||||
if (nvdimm_devs[i] == dev) { | error = bus_generic_attach(dev); | ||||
nv->nv_devs_idx = i; | return (error); | ||||
break; | |||||
} | } | ||||
} | |||||
MPASS(i < nvdimm_devcnt); | |||||
if (ACPI_FAILURE(acpi_GetInteger(handle, "_ADR", &nv->nv_handle))) { | |||||
device_printf(dev, "cannot get handle\n"); | |||||
return (ENXIO); | |||||
} | |||||
status = AcpiGetTable(ACPI_SIG_NFIT, 1, (ACPI_TABLE_HEADER **)&nfitbl); | |||||
if (ACPI_FAILURE(status)) { | |||||
if (bootverbose) | |||||
device_printf(dev, "cannot get NFIT\n"); | |||||
return (ENXIO); | |||||
} | |||||
nvdimm_iterate_nfit(nfitbl, ACPI_NFIT_TYPE_FLUSH_ADDRESS, | |||||
nvdimm_parse_flush_addr, nv); | |||||
AcpiPutTable(&nfitbl->Header); | |||||
return (0); | |||||
} | |||||
static int | static int | ||||
nvdimm_detach(device_t dev) | nvdimm_root_detach(device_t dev) | ||||
{ | { | ||||
struct nvdimm_dev *nv; | device_t *children; | ||||
int i, error, num_children; | |||||
nv = device_get_softc(dev); | error = bus_generic_detach(dev); | ||||
nvdimm_devs[nv->nv_devs_idx] = NULL; | if (error != 0) | ||||
free(nv->nv_flush_addr, M_NVDIMM); | return (error); | ||||
return (0); | error = device_get_children(dev, &children, &num_children); | ||||
if (error != 0) | |||||
return (error); | |||||
for (i = 0; i < num_children; i++) | |||||
free(device_get_ivars(children[i]), M_NVDIMM); | |||||
free(children, M_TEMP); | |||||
error = device_delete_children(dev); | |||||
return (error); | |||||
} | } | ||||
static int | static int | ||||
nvdimm_suspend(device_t dev) | nvdimm_root_read_ivar(device_t dev, device_t child, int index, | ||||
uintptr_t *result) | |||||
{ | { | ||||
if (index < 0 || index >= NVDIMM_ROOT_IVAR_MAX) | |||||
return (ENOENT); | |||||
*result = ((uintptr_t *)device_get_ivars(child))[index]; | |||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
nvdimm_resume(device_t dev) | nvdimm_root_write_ivar(device_t dev, device_t child, int index, | ||||
uintptr_t value) | |||||
{ | { | ||||
if (index < 0 || index >= NVDIMM_ROOT_IVAR_MAX) | |||||
return (ENOENT); | |||||
((uintptr_t *)device_get_ivars(child))[index] = value; | |||||
return (0); | return (0); | ||||
} | } | ||||
static device_method_t nvdimm_methods[] = { | static device_method_t nvdimm_methods[] = { | ||||
DEVMETHOD(device_identify, nvdimm_identify), | |||||
DEVMETHOD(device_probe, nvdimm_probe), | DEVMETHOD(device_probe, nvdimm_probe), | ||||
DEVMETHOD(device_attach, nvdimm_attach), | DEVMETHOD(device_attach, nvdimm_attach), | ||||
DEVMETHOD(device_detach, nvdimm_detach), | DEVMETHOD(device_detach, nvdimm_detach), | ||||
DEVMETHOD(device_suspend, nvdimm_suspend), | DEVMETHOD(device_suspend, nvdimm_suspend), | ||||
DEVMETHOD(device_resume, nvdimm_resume), | DEVMETHOD(device_resume, nvdimm_resume), | ||||
DEVMETHOD_END | DEVMETHOD_END | ||||
}; | }; | ||||
static driver_t nvdimm_driver = { | static driver_t nvdimm_driver = { | ||||
"nvdimm", | "nvdimm", | ||||
nvdimm_methods, | nvdimm_methods, | ||||
sizeof(struct nvdimm_dev), | sizeof(struct nvdimm_dev), | ||||
}; | }; | ||||
static void | static device_method_t nvdimm_root_methods[] = { | ||||
nvdimm_fini(void) | DEVMETHOD(device_probe, nvdimm_root_probe), | ||||
{ | DEVMETHOD(device_attach, nvdimm_root_attach), | ||||
DEVMETHOD(device_detach, nvdimm_root_detach), | |||||
DEVMETHOD(bus_add_child, bus_generic_add_child), | |||||
DEVMETHOD(bus_read_ivar, nvdimm_root_read_ivar), | |||||
DEVMETHOD(bus_write_ivar, nvdimm_root_write_ivar), | |||||
DEVMETHOD_END | |||||
}; | |||||
free(nvdimm_devs, M_NVDIMM); | static driver_t nvdimm_root_driver = { | ||||
nvdimm_devs = NULL; | "nvdimm_root", | ||||
nvdimm_devcnt = 0; | nvdimm_root_methods, | ||||
} | }; | ||||
static int | DRIVER_MODULE(nvdimm_root, acpi, nvdimm_root_driver, nvdimm_root_devclass, NULL, | ||||
nvdimm_modev(struct module *mod, int what, void *arg) | NULL); | ||||
{ | DRIVER_MODULE(nvdimm, nvdimm_root, nvdimm_driver, nvdimm_devclass, NULL, NULL); | ||||
int error; | |||||
switch (what) { | |||||
case MOD_LOAD: | |||||
error = 0; | |||||
break; | |||||
case MOD_UNLOAD: | |||||
nvdimm_fini(); | |||||
error = 0; | |||||
break; | |||||
case MOD_QUIESCE: | |||||
error = 0; | |||||
break; | |||||
default: | |||||
error = EOPNOTSUPP; | |||||
break; | |||||
} | |||||
return (error); | |||||
} | |||||
DRIVER_MODULE(nvdimm, acpi, nvdimm_driver, nvdimm_devclass, nvdimm_modev, NULL); | |||||
MODULE_DEPEND(nvdimm, acpi, 1, 1, 1); | MODULE_DEPEND(nvdimm, acpi, 1, 1, 1); |