On Wed, 2015-06-03 at 16:57 +0200, Christoph Hellwig wrote: > On Mon, Jun 01, 2015 at 08:14:19PM -0400, Dan Williams wrote: > > A libnvdimm bus is the anchor device for registering nvdimm resources and > > interfaces, for example, a character control device, nvdimm devices, > > and I/O region devices. The ACPI NFIT (NVDIMM Firmware Interface Table) > > is one possible platform description for such non-volatile memory > > resources in a system. The nfit.ko driver attaches to the "ACPI0012" > > device that indicates the presence of the NFIT and parses the table to > > register a libnvdimm bus instance. > > Havin lib in a name of a bus seems odd. Why not simply the nvdimm bus? Sorry, that is a typo the bus is indeed an struct nvdimm_bus instance. > Also this seems to both have the generic nvdimm infrastructure as well > as the acpi wiring. Might make sense to split this into two patches? I like to show an initial infrastructure consumer with the initial implementation in one patch. Makes it easier to catch premature implementation of features. > > > +config ACPI_NFIT > > + tristate "ACPI NVDIMM Firmware Interface Table (NFIT)" > > + depends on PHYS_ADDR_T_64BIT > > + depends on BLK_DEV > > + select NVDIMM_DEVICES > > + select LIBNVDIMM > > Is this the right way for the user to chose it? It seems like enabling > the NVMDIMM subsystem would be the obvious choice, and ACPI would > simply enable the table parsing in that case. The NVDIMM sub-system is enabled by an external provider. We have the e820 type-12 provider and the NFIT provider that select LIBNVDIMM. > > +static u8 nfit_uuid[NFIT_UUID_MAX][16]; > > Should this use the uuid_le type? > That would depend on Rafael being open to changing the acpi_str_to_uuid() prototype to take an uuid_le. > > +static const char *spa_type_name(u16 type) > > +{ > > + switch (type) { > > + case NFIT_SPA_VOLATILE: return "volatile"; > > + case NFIT_SPA_PM: return "pmem"; > > + case NFIT_SPA_DCR: return "dimm-control-region"; > > + case NFIT_SPA_BDW: return "block-data-window"; > > + default: return "unknown"; > > Please never put code on the same line as a switch (or goto) label. Ok, fixed. > > > +static void *add_table(struct acpi_nfit_desc *acpi_desc, void *table, const void *end) > > +{ > > + struct device *dev = acpi_desc->dev; > > + struct acpi_nfit_header *hdr; > > + void *err = ERR_PTR(-ENOMEM); > > + > > + if (table >= end) > > + return NULL; > > + > > + hdr = (struct acpi_nfit_header *) table; > > No need to case from void * to another pointer type. Whoops, fixed. > > > + switch (hdr->type) { > > + case ACPI_NFIT_TYPE_SYSTEM_ADDRESS: { > > + struct nfit_spa *nfit_spa = devm_kzalloc(dev, sizeof(*nfit_spa), > > + GFP_KERNEL); > > + struct acpi_nfit_system_address *spa = table; > > + > > + if (!nfit_spa) > > + return err; > > + INIT_LIST_HEAD(&nfit_spa->list); > > + nfit_spa->spa = spa; > > + list_add_tail(&nfit_spa->list, &acpi_desc->spas); > > + dev_dbg(dev, "%s: spa index: %d type: %s\n", __func__, > > + spa->range_index, > > + spa_type_name(nfit_spa_type(spa))); > > If you need local variables inside a switch statement you probably > want to split each case into a separate helper function. Yeah, probably better that way. Done. > > > +static inline struct acpi_nfit_memory_map *__to_nfit_memdev(struct nfit_mem *nfit_mem) > > This line is over 80 characters. I generally don't see the point of fixing up occasional small incursions over 80 characters if it makes the code more amenable to grep. > Also why the odd __-prefix? Later on the series there's a to_nfit_memdev() that takes a struct device *, and this one is an internal helper. > > > new file mode 100644 > > index 000000000000..24b51dbc8215 > > --- /dev/null > > +++ b/drivers/nvdimm/Kconfig > > @@ -0,0 +1,20 @@ > > +menuconfig NVDIMM_DEVICES > > + bool "NVDIMM (Non-Volatile Memory Device) Support" > > + help > > + Generic support for non-volatile memory devices including > > + ACPI-6-NFIT defined resources. On platforms that define an > > + NFIT, or otherwise can discover NVDIMM resources, a libnvdimm > > + bus is registered to advertise PMEM (persistent memory) > > + namespaces (/dev/pmemX) and BLK (sliding mmio window(s)) > > + namespaces (/dev/ndX). A PMEM namespace refers to a memory > > + resource that may span multiple DIMMs and support DAX (see > > + CONFIG_DAX). A BLK namespace refers to an NVDIMM control > > + region which exposes an mmio register set for windowed > > + access mode to non-volatile memory. > > + > > +if NVDIMM_DEVICES > > + > > +config LIBNVDIMM > > + tristate > > + > > +endif > > What different meanings will CONFIG_NVDIMM_DEVICES and CONFIG_LIBNVDIMM > have? Hmm, nothing worthwhile. I'll merge them into one menuconfig CONFIG_LIBNVDIMM tristate. > > > diff --git a/drivers/nvdimm/nd-private.h b/drivers/nvdimm/nd-private.h > > new file mode 100644 > > index 000000000000..e7c4e99a22a4 > > --- /dev/null > > +++ b/drivers/nvdimm/nd-private.h > > No need for -private if you're already under drivers/nvdimm.. This is the private private-header in the sense that it defines infrastructure that is only meant for the core. Drivers (btt, pmem, blk, etc) must not include nd-private.h. I'll rename it "nd-core.h" to make this more clear. > > +#ifndef __ND_PRIVATE_H__ > > +#define __ND_PRIVATE_H__ > > +#include > > +#include > > + > > +struct nvdimm_bus { > > + struct nvdimm_bus_descriptor *nd_desc; > > + struct device dev; > > + int id; > > +}; > > +#endif /* __ND_PRIVATE_H__ */ > > At least so far this header doesn't need libnvdimm.h struct nvdimm_bus_descriptor is defined there and a forward declaration does not make sense since we'll need the rest of the header later in the series. > > > +#ifndef __LIBNVDIMM_H__ > > +#define __LIBNVDIMM_H__ > > +struct nvdimm; > > +struct nvdimm_bus_descriptor; > > +typedef int (*ndctl_fn)(struct nvdimm_bus_descriptor *nd_desc, > > + struct nvdimm *nvdimm, unsigned int cmd, void *buf, > > + unsigned int buf_len); > > + > > +struct nvdimm_bus_descriptor { > > + unsigned long dsm_mask; > > + char *provider_name; > > + ndctl_fn ndctl; > > +}; > > Please provide proper methods that do one thing properly instead of > ioctl-like multiplexers. This *is* the ioctl multiplexer. The generic ioctl implementation in libnvdimm uses the ndctl_fn to call back out to the provider to process the command. In the case of ACPI this maps to a _DSM (device specific method), for nfit_test it's a emulated implementation, and for e820-type-12 this is obviously not implemented. Reflowed patch with these fixes follows... 8<----- Subject: libnvdimm, nfit: initial libnvdimm infrastructure and NFIT support From: Dan Williams A libnvdimm bus is the anchor device for registering nvdimm resources and interfaces, for example, a character control device, nvdimm devices, and I/O region devices. The ACPI NFIT (NVDIMM Firmware Interface Table) is one possible platform description for such non-volatile memory resources in a system. The nfit.ko driver attaches to the "ACPI0012" device that indicates the presence of the NFIT and parses the table to register a struct nvdimm_bus instance. Cc: Cc: Lv Zheng Cc: Robert Moore Cc: Rafael J. Wysocki Acked-by: Jeff Moyer Signed-off-by: Dan Williams --- drivers/Kconfig | 2 drivers/Makefile | 1 drivers/acpi/Kconfig | 14 + drivers/acpi/Makefile | 1 drivers/acpi/nfit.c | 481 +++++++++++++++++++++++++++++++++++++++++++++ drivers/acpi/nfit.h | 89 ++++++++ drivers/nvdimm/Kconfig | 13 + drivers/nvdimm/Makefile | 3 drivers/nvdimm/core.c | 67 ++++++ drivers/nvdimm/nd-core.h | 23 ++ include/linux/libnvdimm.h | 34 +++ 11 files changed, 728 insertions(+) create mode 100644 drivers/acpi/nfit.c create mode 100644 drivers/acpi/nfit.h create mode 100644 drivers/nvdimm/Kconfig create mode 100644 drivers/nvdimm/Makefile create mode 100644 drivers/nvdimm/core.c create mode 100644 drivers/nvdimm/nd-core.h create mode 100644 include/linux/libnvdimm.h diff --git a/drivers/Kconfig b/drivers/Kconfig index c0cc96bab9e7..6e973b8e3a3b 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -182,4 +182,6 @@ source "drivers/thunderbolt/Kconfig" source "drivers/android/Kconfig" +source "drivers/nvdimm/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 46d2554be404..692adf659028 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_FB_INTEL) += video/fbdev/intelfb/ obj-$(CONFIG_PARPORT) += parport/ obj-y += base/ block/ misc/ mfd/ nfc/ +obj-$(CONFIG_LIBNVDIMM) += nvdimm/ obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf/ obj-$(CONFIG_NUBUS) += nubus/ obj-y += macintosh/ diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index ab2cbb51c6aa..300b4ef3712b 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -383,6 +383,20 @@ config ACPI_REDUCED_HARDWARE_ONLY If you are unsure what to do, do not enable this option. +config ACPI_NFIT + tristate "ACPI NVDIMM Firmware Interface Table (NFIT)" + depends on PHYS_ADDR_T_64BIT + depends on BLK_DEV + select LIBNVDIMM + help + Infrastructure to probe ACPI 6 compliant platforms for + NVDIMMs (NFIT) and register a libnvdimm device tree. In + addition to storage devices this also enables libnvdimm to pass + ACPI._DSM messages for platform/dimm configuration. + + To compile this driver as a module, choose M here: + the module will be called nfit. + source "drivers/acpi/apei/Kconfig" config ACPI_EXTLOG diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 8a063e276530..f7e9c92ccdcb 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o obj-$(CONFIG_ACPI_PROCESSOR) += processor.o obj-y += container.o obj-$(CONFIG_ACPI_THERMAL) += thermal.o +obj-$(CONFIG_ACPI_NFIT) += nfit.o obj-y += acpi_memhotplug.o obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o obj-$(CONFIG_ACPI_BATTERY) += battery.o diff --git a/drivers/acpi/nfit.c b/drivers/acpi/nfit.c new file mode 100644 index 000000000000..c400d60018a5 --- /dev/null +++ b/drivers/acpi/nfit.c @@ -0,0 +1,481 @@ +/* + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include "nfit.h" + +static u8 nfit_uuid[NFIT_UUID_MAX][16]; + +static const u8 *to_nfit_uuid(enum nfit_uuids id) +{ + return nfit_uuid[id]; +} + +static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, + struct nvdimm *nvdimm, unsigned int cmd, void *buf, + unsigned int buf_len) +{ + return -ENOTTY; +} + +static const char *spa_type_name(u16 type) +{ + static const char *to_name[] = { + [NFIT_SPA_VOLATILE] = "volatile", + [NFIT_SPA_PM] = "pmem", + [NFIT_SPA_DCR] = "dimm-control-region", + [NFIT_SPA_BDW] = "block-data-window", + [NFIT_SPA_VDISK] = "volatile-disk", + [NFIT_SPA_VCD] = "volatile-cd", + [NFIT_SPA_PDISK] = "persistent-disk", + [NFIT_SPA_PCD] = "persistent-cd", + + }; + + if (type > NFIT_SPA_PCD) + return "unknown"; + + return to_name[type]; +} + +static int nfit_spa_type(struct acpi_nfit_system_address *spa) +{ + int i; + + for (i = 0; i < NFIT_UUID_MAX; i++) + if (memcmp(to_nfit_uuid(i), spa->range_guid, 16) == 0) + return i; + return -1; +} + +static bool add_spa(struct acpi_nfit_desc *acpi_desc, + struct acpi_nfit_system_address *spa) +{ + struct device *dev = acpi_desc->dev; + struct nfit_spa *nfit_spa = devm_kzalloc(dev, sizeof(*nfit_spa), + GFP_KERNEL); + + if (!nfit_spa) + return false; + INIT_LIST_HEAD(&nfit_spa->list); + nfit_spa->spa = spa; + list_add_tail(&nfit_spa->list, &acpi_desc->spas); + dev_dbg(dev, "%s: spa index: %d type: %s\n", __func__, + spa->range_index, + spa_type_name(nfit_spa_type(spa))); + return true; +} + +static bool add_memdev(struct acpi_nfit_desc *acpi_desc, + struct acpi_nfit_memory_map *memdev) +{ + struct device *dev = acpi_desc->dev; + struct nfit_memdev *nfit_memdev = devm_kzalloc(dev, + sizeof(*nfit_memdev), GFP_KERNEL); + + if (!nfit_memdev) + return false; + INIT_LIST_HEAD(&nfit_memdev->list); + nfit_memdev->memdev = memdev; + list_add_tail(&nfit_memdev->list, &acpi_desc->memdevs); + dev_dbg(dev, "%s: memdev handle: %#x spa: %d dcr: %d\n", + __func__, memdev->device_handle, memdev->range_index, + memdev->region_index); + return true; +} + +static bool add_dcr(struct acpi_nfit_desc *acpi_desc, + struct acpi_nfit_control_region *dcr) +{ + struct device *dev = acpi_desc->dev; + struct nfit_dcr *nfit_dcr = devm_kzalloc(dev, sizeof(*nfit_dcr), + GFP_KERNEL); + + if (!nfit_dcr) + return false; + INIT_LIST_HEAD(&nfit_dcr->list); + nfit_dcr->dcr = dcr; + list_add_tail(&nfit_dcr->list, &acpi_desc->dcrs); + dev_dbg(dev, "%s: dcr index: %d windows: %d\n", __func__, + dcr->region_index, dcr->windows); + return true; +} + +static bool add_bdw(struct acpi_nfit_desc *acpi_desc, + struct acpi_nfit_data_region *bdw) +{ + struct device *dev = acpi_desc->dev; + struct nfit_bdw *nfit_bdw = devm_kzalloc(dev, sizeof(*nfit_bdw), + GFP_KERNEL); + + if (!nfit_bdw) + return false; + INIT_LIST_HEAD(&nfit_bdw->list); + nfit_bdw->bdw = bdw; + list_add_tail(&nfit_bdw->list, &acpi_desc->bdws); + dev_dbg(dev, "%s: bdw dcr: %d windows: %d\n", __func__, + bdw->region_index, bdw->windows); + return true; +} + +static void *add_table(struct acpi_nfit_desc *acpi_desc, void *table, const void *end) +{ + struct device *dev = acpi_desc->dev; + struct acpi_nfit_header *hdr; + void *err = ERR_PTR(-ENOMEM); + + if (table >= end) + return NULL; + + hdr = table; + switch (hdr->type) { + case ACPI_NFIT_TYPE_SYSTEM_ADDRESS: + if (!add_spa(acpi_desc, table)) + return err; + break; + case ACPI_NFIT_TYPE_MEMORY_MAP: + if (!add_memdev(acpi_desc, table)) + return err; + break; + case ACPI_NFIT_TYPE_CONTROL_REGION: + if (!add_dcr(acpi_desc, table)) + return err; + break; + case ACPI_NFIT_TYPE_DATA_REGION: + if (!add_bdw(acpi_desc, table)) + return err; + break; + /* TODO */ + case ACPI_NFIT_TYPE_INTERLEAVE: + dev_dbg(dev, "%s: idt\n", __func__); + break; + case ACPI_NFIT_TYPE_FLUSH_ADDRESS: + dev_dbg(dev, "%s: flush\n", __func__); + break; + case ACPI_NFIT_TYPE_SMBIOS: + dev_dbg(dev, "%s: smbios\n", __func__); + break; + default: + dev_err(dev, "unknown table '%d' parsing nfit\n", hdr->type); + break; + } + + return table + hdr->length; +} + +static void nfit_mem_find_spa_bdw(struct acpi_nfit_desc *acpi_desc, + struct nfit_mem *nfit_mem) +{ + u32 device_handle = __to_nfit_memdev(nfit_mem)->device_handle; + u16 dcr_index = nfit_mem->dcr->region_index; + struct nfit_spa *nfit_spa; + + list_for_each_entry(nfit_spa, &acpi_desc->spas, list) { + u16 range_index = nfit_spa->spa->range_index; + int type = nfit_spa_type(nfit_spa->spa); + struct nfit_memdev *nfit_memdev; + + if (type != NFIT_SPA_BDW) + continue; + + list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) { + if (nfit_memdev->memdev->range_index != range_index) + continue; + if (nfit_memdev->memdev->device_handle != device_handle) + continue; + if (nfit_memdev->memdev->region_index != dcr_index) + continue; + + nfit_mem->spa_bdw = nfit_spa->spa; + return; + } + } + + dev_dbg(acpi_desc->dev, "SPA-BDW not found for SPA-DCR %d\n", + nfit_mem->spa_dcr->range_index); + nfit_mem->bdw = NULL; +} + +static int nfit_mem_add(struct acpi_nfit_desc *acpi_desc, + struct nfit_mem *nfit_mem, struct acpi_nfit_system_address *spa) +{ + u16 dcr_index = __to_nfit_memdev(nfit_mem)->region_index; + struct nfit_dcr *nfit_dcr; + struct nfit_bdw *nfit_bdw; + + list_for_each_entry(nfit_dcr, &acpi_desc->dcrs, list) { + if (nfit_dcr->dcr->region_index != dcr_index) + continue; + nfit_mem->dcr = nfit_dcr->dcr; + break; + } + + if (!nfit_mem->dcr) { + dev_dbg(acpi_desc->dev, "SPA %d missing:%s%s\n", spa->range_index, + __to_nfit_memdev(nfit_mem) ? "" : " MEMDEV", + nfit_mem->dcr ? "" : " DCR"); + return -ENODEV; + } + + /* + * We've found enough to create an nvdimm, optionally + * find an associated BDW + */ + list_add(&nfit_mem->list, &acpi_desc->dimms); + + list_for_each_entry(nfit_bdw, &acpi_desc->bdws, list) { + if (nfit_bdw->bdw->region_index != dcr_index) + continue; + nfit_mem->bdw = nfit_bdw->bdw; + break; + } + + if (!nfit_mem->bdw) + return 0; + + nfit_mem_find_spa_bdw(acpi_desc, nfit_mem); + return 0; +} + +static int nfit_mem_dcr_init(struct acpi_nfit_desc *acpi_desc, + struct acpi_nfit_system_address *spa) +{ + struct nfit_mem *nfit_mem, *found; + struct nfit_memdev *nfit_memdev; + int type = nfit_spa_type(spa); + u16 dcr_index; + + switch (type) { + case NFIT_SPA_DCR: + case NFIT_SPA_PM: + break; + default: + return 0; + } + + list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) { + int rc; + + if (nfit_memdev->memdev->range_index != spa->range_index) + continue; + found = NULL; + dcr_index = nfit_memdev->memdev->region_index; + list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) + if (__to_nfit_memdev(nfit_mem)->region_index == dcr_index) { + found = nfit_mem; + break; + } + + if (found) + nfit_mem = found; + else { + nfit_mem = devm_kzalloc(acpi_desc->dev, + sizeof(*nfit_mem), GFP_KERNEL); + if (!nfit_mem) + return -ENOMEM; + INIT_LIST_HEAD(&nfit_mem->list); + } + + if (type == NFIT_SPA_DCR) { + /* multiple dimms may share a SPA when interleaved */ + nfit_mem->spa_dcr = spa; + nfit_mem->memdev_dcr = nfit_memdev->memdev; + } else { + /* + * A single dimm may belong to multiple SPA-PM + * ranges, record at least one in addition to + * any SPA-DCR range. + */ + nfit_mem->memdev_pmem = nfit_memdev->memdev; + } + + if (found) + continue; + + rc = nfit_mem_add(acpi_desc, nfit_mem, spa); + if (rc) + return rc; + } + + return 0; +} + +static int nfit_mem_cmp(void *priv, struct list_head *__a, struct list_head *__b) +{ + struct nfit_mem *a = container_of(__a, typeof(*a), list); + struct nfit_mem *b = container_of(__b, typeof(*b), list); + u32 handleA, handleB; + + handleA = __to_nfit_memdev(a)->device_handle; + handleB = __to_nfit_memdev(b)->device_handle; + if (handleA < handleB) + return -1; + else if (handleA > handleB) + return 1; + return 0; +} + +static int nfit_mem_init(struct acpi_nfit_desc *acpi_desc) +{ + struct nfit_spa *nfit_spa; + + /* + * For each SPA-DCR or SPA-PMEM address range find its + * corresponding MEMDEV(s). From each MEMDEV find the + * corresponding DCR. Then, if we're operating on a SPA-DCR, + * try to find a SPA-BDW and a corresponding BDW that references + * the DCR. Throw it all into an nfit_mem object. Note, that + * BDWs are optional. + */ + list_for_each_entry(nfit_spa, &acpi_desc->spas, list) { + int rc; + + rc = nfit_mem_dcr_init(acpi_desc, nfit_spa->spa); + if (rc) + return rc; + } + + list_sort(NULL, &acpi_desc->dimms, nfit_mem_cmp); + + return 0; +} + +static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz) +{ + struct device *dev = acpi_desc->dev; + const void *end; + u8 *data; + + INIT_LIST_HEAD(&acpi_desc->spas); + INIT_LIST_HEAD(&acpi_desc->dcrs); + INIT_LIST_HEAD(&acpi_desc->bdws); + INIT_LIST_HEAD(&acpi_desc->memdevs); + INIT_LIST_HEAD(&acpi_desc->dimms); + + data = (u8 *) acpi_desc->nfit; + end = data + sz; + data += sizeof(struct acpi_table_nfit); + while (!IS_ERR_OR_NULL(data)) + data = add_table(acpi_desc, data, end); + + if (IS_ERR(data)) { + dev_dbg(dev, "%s: nfit table parsing error: %ld\n", __func__, + PTR_ERR(data)); + return PTR_ERR(data); + } + + if (nfit_mem_init(acpi_desc) != 0) + return -ENOMEM; + + return 0; +} + +static int acpi_nfit_add(struct acpi_device *adev) +{ + struct nvdimm_bus_descriptor *nd_desc; + struct acpi_nfit_desc *acpi_desc; + struct device *dev = &adev->dev; + struct acpi_table_header *tbl; + acpi_status status = AE_OK; + acpi_size sz; + int rc; + + status = acpi_get_table_with_size("NFIT", 0, &tbl, &sz); + if (ACPI_FAILURE(status)) { + dev_err(dev, "failed to find NFIT\n"); + return -ENXIO; + } + + acpi_desc = devm_kzalloc(dev, sizeof(*acpi_desc), GFP_KERNEL); + if (!acpi_desc) + return -ENOMEM; + + dev_set_drvdata(dev, acpi_desc); + acpi_desc->dev = dev; + acpi_desc->nfit = (struct acpi_table_nfit *) tbl; + nd_desc = &acpi_desc->nd_desc; + nd_desc->provider_name = "ACPI.NFIT"; + nd_desc->ndctl = acpi_nfit_ctl; + + acpi_desc->nvdimm_bus = nvdimm_bus_register(dev, nd_desc); + if (!acpi_desc->nvdimm_bus) + return -ENXIO; + + rc = acpi_nfit_init(acpi_desc, sz); + if (rc) { + nvdimm_bus_unregister(acpi_desc->nvdimm_bus); + return rc; + } + return 0; +} + +static int acpi_nfit_remove(struct acpi_device *adev) +{ + struct acpi_nfit_desc *acpi_desc = dev_get_drvdata(&adev->dev); + + nvdimm_bus_unregister(acpi_desc->nvdimm_bus); + return 0; +} + +static const struct acpi_device_id acpi_nfit_ids[] = { + { "ACPI0012", 0 }, + { "", 0 }, +}; +MODULE_DEVICE_TABLE(acpi, acpi_nfit_ids); + +static struct acpi_driver acpi_nfit_driver = { + .name = KBUILD_MODNAME, + .ids = acpi_nfit_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = acpi_nfit_add, + .remove = acpi_nfit_remove, + }, +}; + +static __init int nfit_init(void) +{ + BUILD_BUG_ON(sizeof(struct acpi_table_nfit) != 40); + BUILD_BUG_ON(sizeof(struct acpi_nfit_system_address) != 56); + BUILD_BUG_ON(sizeof(struct acpi_nfit_memory_map) != 48); + BUILD_BUG_ON(sizeof(struct acpi_nfit_interleave) != 20); + BUILD_BUG_ON(sizeof(struct acpi_nfit_smbios) != 9); + BUILD_BUG_ON(sizeof(struct acpi_nfit_control_region) != 80); + BUILD_BUG_ON(sizeof(struct acpi_nfit_data_region) != 40); + + acpi_str_to_uuid(UUID_VOLATILE_MEMORY, nfit_uuid[NFIT_SPA_VOLATILE]); + acpi_str_to_uuid(UUID_PERSISTENT_MEMORY, nfit_uuid[NFIT_SPA_PM]); + acpi_str_to_uuid(UUID_CONTROL_REGION, nfit_uuid[NFIT_SPA_DCR]); + acpi_str_to_uuid(UUID_DATA_REGION, nfit_uuid[NFIT_SPA_BDW]); + acpi_str_to_uuid(UUID_VOLATILE_VIRTUAL_DISK, nfit_uuid[NFIT_SPA_VDISK]); + acpi_str_to_uuid(UUID_VOLATILE_VIRTUAL_CD, nfit_uuid[NFIT_SPA_VCD]); + acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_DISK, nfit_uuid[NFIT_SPA_PDISK]); + acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_CD, nfit_uuid[NFIT_SPA_PCD]); + acpi_str_to_uuid(UUID_NFIT_BUS, nfit_uuid[NFIT_DEV_BUS]); + acpi_str_to_uuid(UUID_NFIT_DIMM, nfit_uuid[NFIT_DEV_DIMM]); + + return acpi_bus_register_driver(&acpi_nfit_driver); +} + +static __exit void nfit_exit(void) +{ + acpi_bus_unregister_driver(&acpi_nfit_driver); +} + +module_init(nfit_init); +module_exit(nfit_exit); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Intel Corporation"); diff --git a/drivers/acpi/nfit.h b/drivers/acpi/nfit.h new file mode 100644 index 000000000000..2d98cc1b7b6c --- /dev/null +++ b/drivers/acpi/nfit.h @@ -0,0 +1,89 @@ +/* + * NVDIMM Firmware Interface Table - NFIT + * + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#ifndef __NFIT_H__ +#define __NFIT_H__ +#include +#include +#include +#include +#include + +#define UUID_NFIT_BUS "2f10e7a4-9e91-11e4-89d3-123b93f75cba" +#define UUID_NFIT_DIMM "4309ac30-0d11-11e4-9191-0800200c9a66" + +enum nfit_uuids { + NFIT_SPA_VOLATILE, + NFIT_SPA_PM, + NFIT_SPA_DCR, + NFIT_SPA_BDW, + NFIT_SPA_VDISK, + NFIT_SPA_VCD, + NFIT_SPA_PDISK, + NFIT_SPA_PCD, + NFIT_DEV_BUS, + NFIT_DEV_DIMM, + NFIT_UUID_MAX, +}; + +struct nfit_spa { + struct acpi_nfit_system_address *spa; + struct list_head list; +}; + +struct nfit_dcr { + struct acpi_nfit_control_region *dcr; + struct list_head list; +}; + +struct nfit_bdw { + struct acpi_nfit_data_region *bdw; + struct list_head list; +}; + +struct nfit_memdev { + struct acpi_nfit_memory_map *memdev; + struct list_head list; +}; + +/* assembled tables for a given dimm/memory-device */ +struct nfit_mem { + struct acpi_nfit_memory_map *memdev_dcr; + struct acpi_nfit_memory_map *memdev_pmem; + struct acpi_nfit_control_region *dcr; + struct acpi_nfit_data_region *bdw; + struct acpi_nfit_system_address *spa_dcr; + struct acpi_nfit_system_address *spa_bdw; + struct list_head list; +}; + +struct acpi_nfit_desc { + struct nvdimm_bus_descriptor nd_desc; + struct acpi_table_nfit *nfit; + struct list_head memdevs; + struct list_head dimms; + struct list_head spas; + struct list_head dcrs; + struct list_head bdws; + struct nvdimm_bus *nvdimm_bus; + struct device *dev; +}; + +static inline struct acpi_nfit_memory_map *__to_nfit_memdev(struct nfit_mem *nfit_mem) +{ + if (nfit_mem->memdev_dcr) + return nfit_mem->memdev_dcr; + return nfit_mem->memdev_pmem; +} +#endif /* __NFIT_H__ */ diff --git a/drivers/nvdimm/Kconfig b/drivers/nvdimm/Kconfig new file mode 100644 index 000000000000..4f2291938ea0 --- /dev/null +++ b/drivers/nvdimm/Kconfig @@ -0,0 +1,13 @@ +config LIBNVDIMM + tristate "NVDIMM (Non-Volatile Memory Device) Support" + help + Generic support for non-volatile memory devices including + ACPI-6-NFIT defined resources. On platforms that define an + NFIT, or otherwise can discover NVDIMM resources, a libnvdimm + bus is registered to advertise PMEM (persistent memory) + namespaces (/dev/pmemX) and BLK (sliding mmio window(s)) + namespaces (/dev/ndX). A PMEM namespace refers to a memory + resource that may span multiple DIMMs and support DAX (see + CONFIG_DAX). A BLK namespace refers to an NVDIMM control + region which exposes an mmio register set for windowed + access mode to non-volatile memory. diff --git a/drivers/nvdimm/Makefile b/drivers/nvdimm/Makefile new file mode 100644 index 000000000000..10bc7af47992 --- /dev/null +++ b/drivers/nvdimm/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_LIBNVDIMM) += libnvdimm.o + +libnvdimm-y := core.o diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c new file mode 100644 index 000000000000..5ccd500ffd4f --- /dev/null +++ b/drivers/nvdimm/core.c @@ -0,0 +1,67 @@ +/* + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include "nd-core.h" + +static DEFINE_IDA(nd_ida); + +static void nvdimm_bus_release(struct device *dev) +{ + struct nvdimm_bus *nvdimm_bus = container_of(dev, struct nvdimm_bus, dev); + + ida_simple_remove(&nd_ida, nvdimm_bus->id); + kfree(nvdimm_bus); +} + +struct nvdimm_bus *nvdimm_bus_register(struct device *parent, + struct nvdimm_bus_descriptor *nd_desc) +{ + struct nvdimm_bus *nvdimm_bus = kzalloc(sizeof(*nvdimm_bus), GFP_KERNEL); + int rc; + + if (!nvdimm_bus) + return NULL; + nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); + if (nvdimm_bus->id < 0) { + kfree(nvdimm_bus); + return NULL; + } + nvdimm_bus->nd_desc = nd_desc; + nvdimm_bus->dev.parent = parent; + nvdimm_bus->dev.release = nvdimm_bus_release; + dev_set_name(&nvdimm_bus->dev, "ndbus%d", nvdimm_bus->id); + rc = device_register(&nvdimm_bus->dev); + if (rc) { + dev_dbg(&nvdimm_bus->dev, "device registration failed: %d\n", rc); + put_device(&nvdimm_bus->dev); + return NULL; + } + + return nvdimm_bus; +} +EXPORT_SYMBOL_GPL(nvdimm_bus_register); + +void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus) +{ + if (!nvdimm_bus) + return; + device_unregister(&nvdimm_bus->dev); +} +EXPORT_SYMBOL_GPL(nvdimm_bus_unregister); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Intel Corporation"); diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h new file mode 100644 index 000000000000..e7c4e99a22a4 --- /dev/null +++ b/drivers/nvdimm/nd-core.h @@ -0,0 +1,23 @@ +/* + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#ifndef __ND_PRIVATE_H__ +#define __ND_PRIVATE_H__ +#include +#include + +struct nvdimm_bus { + struct nvdimm_bus_descriptor *nd_desc; + struct device dev; + int id; +}; +#endif /* __ND_PRIVATE_H__ */ diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h new file mode 100644 index 000000000000..2b3c63950c91 --- /dev/null +++ b/include/linux/libnvdimm.h @@ -0,0 +1,34 @@ +/* + * libnvdimm - Non-volatile-memory Devices Subsystem + * + * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#ifndef __LIBNVDIMM_H__ +#define __LIBNVDIMM_H__ +struct nvdimm; +struct nvdimm_bus_descriptor; +typedef int (*ndctl_fn)(struct nvdimm_bus_descriptor *nd_desc, + struct nvdimm *nvdimm, unsigned int cmd, void *buf, + unsigned int buf_len); + +struct nvdimm_bus_descriptor { + unsigned long dsm_mask; + char *provider_name; + ndctl_fn ndctl; +}; + +struct device; +struct nvdimm_bus; +struct nvdimm_bus *nvdimm_bus_register(struct device *parent, + struct nvdimm_bus_descriptor *nfit_desc); +void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus); +#endif /* __LIBNVDIMM_H__ */