1/ Autodetect an NFIT table for the ACPI namespace device with _HID of "ACPI0012" 2/ libnd bus registration The NFIT provided by ACPI is one possible method by which platforms will discover NVDIMM resources. However, the intent of the nd_bus_descriptor abstraction is to abstract "provider" specific details, leaving libnd to be independent of the specific NVDIMM resource discovery mechanism. This flexibility is later exploited later to implement custom-defined nd buses. Cc: <linux-acpi@xxxxxxxxxxxxxxx> Cc: Robert Moore <robert.moore@xxxxxxxxx> Cc: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx> Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/block/Kconfig | 2 drivers/block/Makefile | 1 drivers/block/nd/Kconfig | 40 +++ drivers/block/nd/Makefile | 6 + drivers/block/nd/acpi.c | 475 +++++++++++++++++++++++++++++++++++++++++ drivers/block/nd/acpi_nfit.h | 254 ++++++++++++++++++++++ drivers/block/nd/core.c | 67 ++++++ drivers/block/nd/libnd.h | 33 +++ drivers/block/nd/nd-private.h | 23 ++ 9 files changed, 901 insertions(+) create mode 100644 drivers/block/nd/Kconfig create mode 100644 drivers/block/nd/Makefile create mode 100644 drivers/block/nd/acpi.c create mode 100644 drivers/block/nd/acpi_nfit.h create mode 100644 drivers/block/nd/core.c create mode 100644 drivers/block/nd/libnd.h create mode 100644 drivers/block/nd/nd-private.h diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index eb1fed5bd516..dfe40e5ca9bd 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -321,6 +321,8 @@ config BLK_DEV_NVME To compile this driver as a module, choose M here: the module will be called nvme. +source "drivers/block/nd/Kconfig" + config BLK_DEV_SKD tristate "STEC S1120 Block Driver" depends on PCI diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 9cc6c18a1c7e..07a6acecf4d8 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_CDROM_PKTCDVD) += pktcdvd.o obj-$(CONFIG_MG_DISK) += mg_disk.o obj-$(CONFIG_SUNVDC) += sunvdc.o obj-$(CONFIG_BLK_DEV_NVME) += nvme.o +obj-$(CONFIG_ND_DEVICES) += nd/ obj-$(CONFIG_BLK_DEV_SKD) += skd.o obj-$(CONFIG_BLK_DEV_OSD) += osdblk.o diff --git a/drivers/block/nd/Kconfig b/drivers/block/nd/Kconfig new file mode 100644 index 000000000000..6d5d6b732f82 --- /dev/null +++ b/drivers/block/nd/Kconfig @@ -0,0 +1,40 @@ +menuconfig ND_DEVICES + bool "NVDIMM Support" + depends on PHYS_ADDR_T_64BIT + 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 libnd + 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 ND_DEVICES + +config LIBND + tristate "LIBND: libnd device driver support" + help + Platform agnostic device model for a libnd bus. Publishes + resources for a PMEM (persistent-memory) driver and/or BLK + (sliding mmio window(s)) driver to attach. Exposes a device + topology under a "ndX" bus device, a "/dev/ndctlX" bus-ioctl + message passing interface, and a "/dev/nmemX" dimm-ioctl + message interface for each memory device registered on the + bus. instance. A userspace library "ndctl" provides an API + to enumerate/manage this subsystem. + +config ND_ACPI + tristate "ACPI: NFIT to libnd bus support" + select LIBND + depends on ACPI + help + Infrastructure to probe ACPI 6 compliant platforms for + NVDIMMs (NFIT) and register a libnd device tree. In + addition to storage devices this also enables libnd craft + ACPI._DSM messages for platform/dimm configuration. + +endif diff --git a/drivers/block/nd/Makefile b/drivers/block/nd/Makefile new file mode 100644 index 000000000000..944b5947c0cb --- /dev/null +++ b/drivers/block/nd/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_LIBND) += libnd.o +obj-$(CONFIG_ND_ACPI) += nd_acpi.o + +nd_acpi-y := acpi.o + +libnd-y := core.o diff --git a/drivers/block/nd/acpi.c b/drivers/block/nd/acpi.c new file mode 100644 index 000000000000..9f0b24390d1b --- /dev/null +++ b/drivers/block/nd/acpi.c @@ -0,0 +1,475 @@ +/* + * 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 <linux/list_sort.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/acpi.h> +#include "acpi_nfit.h" +#include "libnd.h" + +static bool warn_checksum; +module_param(warn_checksum, bool, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(warn_checksum, "Turn checksum errors into warnings"); + +enum { + NFIT_ACPI_NOTIFY_TABLE = 0x80, +}; + +static int nd_acpi_ctl(struct nd_bus_descriptor *nd_desc, + struct nd_dimm *nd_dimm, unsigned int cmd, void *buf, + unsigned int buf_len) +{ + return -ENOTTY; +} + +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"; + } +} + +static int nfit_spa_type(struct acpi_nfit_spa *spa) +{ + if (memcmp(&nfit_spa_uuid_volatile, spa->type_uuid, 16) == 0) + return NFIT_SPA_VOLATILE; + + if (memcmp(&nfit_spa_uuid_pm, spa->type_uuid, 16) == 0) + return NFIT_SPA_PM; + + if (memcmp(&nfit_spa_uuid_dcr, spa->type_uuid, 16) == 0) + return NFIT_SPA_DCR; + + if (memcmp(&nfit_spa_uuid_bdw, spa->type_uuid, 16) == 0) + return NFIT_SPA_BDW; + + if (memcmp(&nfit_spa_uuid_vdisk, spa->type_uuid, 16) == 0) + return NFIT_SPA_VDISK; + + if (memcmp(&nfit_spa_uuid_vcd, spa->type_uuid, 16) == 0) + return NFIT_SPA_VCD; + + if (memcmp(&nfit_spa_uuid_pdisk, spa->type_uuid, 16) == 0) + return NFIT_SPA_PDISK; + + if (memcmp(&nfit_spa_uuid_pcd, spa->type_uuid, 16) == 0) + return NFIT_SPA_PCD; + + return -1; +} + +struct nfit_table_header { + __le16 type; + __le16 length; +}; + +static void *add_table(struct acpi_nfit_desc *acpi_desc, void *table, const void *end) +{ + struct device *dev = acpi_desc->dev; + struct nfit_table_header *hdr; + void *err = ERR_PTR(-ENOMEM); + + if (table >= end) + return NULL; + + hdr = (struct nfit_table_header *) table; + switch (hdr->type) { + case NFIT_TABLE_SPA: { + struct nfit_spa *nfit_spa = devm_kzalloc(dev, sizeof(*nfit_spa), + GFP_KERNEL); + struct acpi_nfit_spa *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->spa_index, + spa_type_name(nfit_spa_type(spa))); + break; + } + case NFIT_TABLE_MEM: { + struct nfit_memdev *nfit_memdev = devm_kzalloc(dev, + sizeof(*nfit_memdev), GFP_KERNEL); + struct acpi_nfit_memdev *memdev = table; + + if (!nfit_memdev) + return err; + 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->nfit_handle, memdev->spa_index, + memdev->dcr_index); + break; + } + case NFIT_TABLE_DCR: { + struct nfit_dcr *nfit_dcr = devm_kzalloc(dev, sizeof(*nfit_dcr), + GFP_KERNEL); + struct acpi_nfit_dcr *dcr = table; + + if (!nfit_dcr) + return err; + 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 num_bcw: %d\n", __func__, + dcr->dcr_index, dcr->num_bcw); + break; + } + case NFIT_TABLE_BDW: { + struct nfit_bdw *nfit_bdw = devm_kzalloc(dev, sizeof(*nfit_bdw), + GFP_KERNEL); + struct acpi_nfit_bdw *bdw = table; + + if (!nfit_bdw) + return err; + 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 num_bdw: %d\n", __func__, + bdw->dcr_index, bdw->num_bdw); + break; + } + /* TODO */ + case NFIT_TABLE_IDT: + dev_dbg(dev, "%s: idt\n", __func__); + break; + case NFIT_TABLE_FLUSH: + dev_dbg(dev, "%s: flush\n", __func__); + break; + case NFIT_TABLE_SMBIOS: + dev_dbg(dev, "%s: smbios\n", __func__); + break; + default: + dev_err(dev, "unknown table '%d' parsing nfit\n", hdr->type); + return ERR_PTR(-ENXIO); + } + + return table + hdr->length; +} + +static void nfit_mem_find_spa_bdw(struct acpi_nfit_desc *acpi_desc, + struct nfit_mem *nfit_mem) +{ + u32 nfit_handle = __to_nfit_memdev(nfit_mem)->nfit_handle; + u16 dcr_index = nfit_mem->dcr->dcr_index; + struct nfit_spa *nfit_spa; + + list_for_each_entry(nfit_spa, &acpi_desc->spas, list) { + u16 spa_index = nfit_spa->spa->spa_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->spa_index != spa_index) + continue; + if (nfit_memdev->memdev->nfit_handle != nfit_handle) + continue; + if (nfit_memdev->memdev->dcr_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->spa_index); + nfit_mem->bdw = NULL; +} + +static int nfit_mem_add(struct acpi_nfit_desc *acpi_desc, + struct nfit_mem *nfit_mem, struct acpi_nfit_spa *spa) +{ + u16 dcr_index = __to_nfit_memdev(nfit_mem)->dcr_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->dcr_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->spa_index, + __to_nfit_memdev(nfit_mem) ? "" : " MEMDEV", + nfit_mem->dcr ? "" : " DCR"); + return -ENODEV; + } + + /* + * We've found enough to create an nd_dimm, 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->dcr_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_spa *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->spa_index != spa->spa_index) + continue; + found = NULL; + dcr_index = nfit_memdev->memdev->dcr_index; + list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) + if (__to_nfit_memdev(nfit_mem)->dcr_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)->nfit_handle; + handleB = __to_nfit_memdev(b)->nfit_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 nd_acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz) +{ + struct device *dev = acpi_desc->dev; + const void *end; + u8 *data, sum; + acpi_size i; + + 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; + for (i = 0, sum = 0; i < sz; i++) + sum += readb(data + i); + if (sum != 0 && !warn_checksum) { + dev_dbg(dev, "%s: nfit checksum failure\n", __func__); + return -ENXIO; + } + WARN_TAINT_ONCE(sum != 0, TAINT_FIRMWARE_WORKAROUND, + "nfit checksum failure, continuing...\n"); + + end = data + sz; + data += sizeof(struct acpi_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 nd_acpi_add(struct acpi_device *adev) +{ + struct nd_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_nfit *) tbl; + nd_desc = &acpi_desc->nd_desc; + nd_desc->provider_name = "ACPI.NFIT"; + nd_desc->ndctl = nd_acpi_ctl; + + acpi_desc->nd_bus = nd_bus_register(dev, nd_desc); + if (!acpi_desc->nd_bus) + return -ENXIO; + + rc = nd_acpi_nfit_init(acpi_desc, sz); + if (rc) { + nd_bus_unregister(acpi_desc->nd_bus); + return rc; + } + return 0; +} + +static int nd_acpi_remove(struct acpi_device *adev) +{ + struct acpi_nfit_desc *acpi_desc = dev_get_drvdata(&adev->dev); + + nd_bus_unregister(acpi_desc->nd_bus); + return 0; +} + +static void nd_acpi_notify(struct acpi_device *adev, u32 event) +{ + /* TODO: handle ACPI_NOTIFY_BUS_CHECK notification */ + dev_dbg(&adev->dev, "%s: event: %d\n", __func__, event); +} + +static const struct acpi_device_id nd_acpi_ids[] = { + { "ACPI0012", 0 }, + { "", 0 }, +}; +MODULE_DEVICE_TABLE(acpi, nd_acpi_ids); + +static struct acpi_driver nd_acpi_driver = { + .name = KBUILD_MODNAME, + .ids = nd_acpi_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = nd_acpi_add, + .remove = nd_acpi_remove, + .notify = nd_acpi_notify + }, +}; + +static __init int nd_acpi_init(void) +{ + BUILD_BUG_ON(sizeof(struct acpi_nfit) != 40); + BUILD_BUG_ON(sizeof(struct acpi_nfit_spa) != 56); + BUILD_BUG_ON(sizeof(struct acpi_nfit_memdev) != 48); + BUILD_BUG_ON(sizeof(struct acpi_nfit_idt) != 16); + BUILD_BUG_ON(sizeof(struct acpi_nfit_smbios) != 8); + BUILD_BUG_ON(sizeof(struct acpi_nfit_dcr) != 80); + BUILD_BUG_ON(sizeof(struct acpi_nfit_bdw) != 40); + + return acpi_bus_register_driver(&nd_acpi_driver); +} + +static __exit void nd_acpi_exit(void) +{ + acpi_bus_unregister_driver(&nd_acpi_driver); +} + +module_init(nd_acpi_init); +module_exit(nd_acpi_exit); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Intel Corporation"); diff --git a/drivers/block/nd/acpi_nfit.h b/drivers/block/nd/acpi_nfit.h new file mode 100644 index 000000000000..e0b0f12736bf --- /dev/null +++ b/drivers/block/nd/acpi_nfit.h @@ -0,0 +1,254 @@ +/* + * 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 <linux/types.h> +#include <linux/uuid.h> +#include <linux/acpi.h> +#include "libnd.h" + +static const uuid_le nfit_spa_uuid_volatile __maybe_unused = UUID_LE(0x7305944f, + 0xfdda, 0x44e3, 0xb1, 0x6c, 0x3f, 0x22, 0xd2, 0x52, 0xe5, 0xd0); + +static const uuid_le nfit_spa_uuid_pm __maybe_unused = UUID_LE(0x66f0d379, + 0xb4f3, 0x4074, 0xac, 0x43, 0x0d, 0x33, 0x18, 0xb7, 0x8c, 0xdb); + +static const uuid_le nfit_spa_uuid_dcr __maybe_unused = UUID_LE(0x92f701f6, + 0x13b4, 0x405d, 0x91, 0x0b, 0x29, 0x93, 0x67, 0xe8, 0x23, 0x4c); + +static const uuid_le nfit_spa_uuid_bdw __maybe_unused = UUID_LE(0x91af0530, + 0x5d86, 0x470e, 0xa6, 0xb0, 0x0a, 0x2d, 0xb9, 0x40, 0x82, 0x49); + +static const uuid_le nfit_spa_uuid_vdisk __maybe_unused = UUID_LE(0x77ab535a, + 0x45fc, 0x624b, 0x55, 0x60, 0xf7, 0xb2, 0x81, 0xd1, 0xf9, 0x6e); + +static const uuid_le nfit_spa_uuid_vcd __maybe_unused = UUID_LE(0x3d5abd30, + 0x4175, 0x87ce, 0x6d, 0x64, 0xd2, 0xad, 0xe5, 0x23, 0xc4, 0xbb); + +static const uuid_le nfit_spa_uuid_pdisk __maybe_unused = UUID_LE(0x5cea02c9, + 0x4d07, 0x69d3, 0x26, 0x9f, 0x44, 0x96, 0xfb, 0xe0, 0x96, 0xf9); + +static const uuid_le nfit_spa_uuid_pcd __maybe_unused = UUID_LE(0x08018188, + 0x42cd, 0xbb48, 0x10, 0x0f, 0x53, 0x87, 0xd5, 0x3d, 0xed, 0x3d); + +enum { + NFIT_TABLE_SPA = 0, + NFIT_TABLE_MEM = 1, + NFIT_TABLE_IDT = 2, + NFIT_TABLE_SMBIOS = 3, + NFIT_TABLE_DCR = 4, + NFIT_TABLE_BDW = 5, + NFIT_TABLE_FLUSH = 6, + NFIT_SPA_VOLATILE = 0, + NFIT_SPA_PM = 1, + NFIT_SPA_DCR = 2, + NFIT_SPA_BDW = 3, + NFIT_SPA_VDISK = 4, + NFIT_SPA_VCD = 5, + NFIT_SPA_PDISK = 6, + NFIT_SPA_PCD = 7, + NFIT_SPAF_DCR_HOT_ADD = 1 << 0, + NFIT_SPAF_PDVALID = 1 << 1, + NFIT_MEMF_SAVE_FAIL = 1 << 0, + NFIT_MEMF_RESTORE_FAIL = 1 << 1, + NFIT_MEMF_FLUSH_FAIL = 1 << 2, + NFIT_MEMF_UNARMED = 1 << 3, + NFIT_MEMF_NOTIFY_SMART = 1 << 4, + NFIT_MEMF_SMART_READY = 1 << 5, + NFIT_DCRF_BUFFERED = 1 << 0, +}; + +/** + * struct acpi_nfit - Nvdimm Firmware Interface Table + * @signature: "NFIT" + * @length: sum of size of this table plus all appended subtables + */ +struct acpi_nfit { + u8 signature[4]; + u32 length; + u8 revision; + u8 checksum; + u8 oemid[6]; + u64 oem_tbl_id; + u32 oem_revision; + u32 creator_id; + u32 creator_revision; + u32 reserved; +}; + +/** + * struct acpi_nfit_spa - System Physical Address Range Descriptor Table + */ +struct acpi_nfit_spa { + u16 type; + u16 length; + u16 spa_index; + u16 flags; + u32 reserved; + u32 proximity_domain; + u8 type_uuid[16]; + u64 spa_base; + u64 spa_length; + u64 mem_attr; +}; + +/** + * struct acpi_nfit_mem - Memory Device to SPA Mapping Table + */ +struct acpi_nfit_memdev { + u16 type; + u16 length; + u32 nfit_handle; + u16 phys_id; + u16 region_id; + u16 spa_index; + u16 dcr_index; + u64 region_len; + u64 region_spa_offset; + u64 region_dpa; + u16 idt_index; + u16 interleave_ways; + u16 flags; + u16 reserved; +}; + +/** + * struct acpi_nfit_idt - Interleave description Table + */ +struct acpi_nfit_idt { + u16 type; + u16 length; + u16 idt_index; + u16 reserved; + u32 num_lines; + u32 line_size; + u32 line_offset[0]; +}; + +/** + * struct acpi_nfit_smbios - SMBIOS Management Information Table + */ +struct acpi_nfit_smbios { + u16 type; + u16 length; + u32 reserved; + u8 data[0]; +}; + +/** + * struct acpi_nfit_dcr - NVDIMM Control Region Table + * @fic: Format Interface Code + * @cmd_offset: command registers relative to block control window + * @status_offset: status registers relative to block control window + */ +struct acpi_nfit_dcr { + u16 type; + u16 length; + u16 dcr_index; + u16 vendor_id; + u16 device_id; + u16 revision_id; + u16 sub_vendor_id; + u16 sub_device_id; + u16 sub_revision_id; + u8 reserved[6]; + u32 serial_number; + u16 fic; + u16 num_bcw; + u64 bcw_size; + u64 cmd_offset; + u64 cmd_size; + u64 status_offset; + u64 status_size; + u16 flags; + u8 reserved2[6]; +}; + +/** + * struct acpi_nfit_bdw - NVDIMM Block Data Window Region Table + */ +struct acpi_nfit_bdw { + u16 type; + u16 length; + u16 dcr_index; + u16 num_bdw; + u64 bdw_offset; + u64 bdw_size; + u64 blk_capacity; + u64 blk_offset; +}; + +/** + * struct acpi_nfit_flush - Flush Hint Address Structure + */ +struct acpi_nfit_flush { + u16 type; + u16 length; + u32 nfit_handle; + u16 num_hints; + u8 reserved[6]; + u64 hint_addr[0]; +}; + +struct nfit_spa { + struct acpi_nfit_spa *spa; + struct list_head list; +}; + +struct nfit_dcr { + struct acpi_nfit_dcr *dcr; + struct list_head list; +}; + +struct nfit_bdw { + struct acpi_nfit_bdw *bdw; + struct list_head list; +}; + +struct nfit_memdev { + struct acpi_nfit_memdev *memdev; + struct list_head list; +}; + +/* assembled tables for a given dimm/memory-device */ +struct nfit_mem { + struct acpi_nfit_memdev *memdev_dcr; + struct acpi_nfit_memdev *memdev_pmem; + struct acpi_nfit_dcr *dcr; + struct acpi_nfit_bdw *bdw; + struct acpi_nfit_spa *spa_dcr; + struct acpi_nfit_spa *spa_bdw; + struct list_head list; +}; + +struct acpi_nfit_desc { + struct nd_bus_descriptor nd_desc; + struct acpi_nfit *nfit; + struct list_head memdevs; + struct list_head dimms; + struct list_head spas; + struct list_head dcrs; + struct list_head bdws; + struct nd_bus *nd_bus; + struct device *dev; +}; + +static inline struct acpi_nfit_memdev *__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/block/nd/core.c b/drivers/block/nd/core.c new file mode 100644 index 000000000000..3cccdbc0f3b7 --- /dev/null +++ b/drivers/block/nd/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 <linux/export.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include "nd-private.h" +#include "libnd.h" + +static DEFINE_IDA(nd_ida); + +static void nd_bus_release(struct device *dev) +{ + struct nd_bus *nd_bus = container_of(dev, struct nd_bus, dev); + + ida_simple_remove(&nd_ida, nd_bus->id); + kfree(nd_bus); +} + +struct nd_bus *nd_bus_register(struct device *parent, + struct nd_bus_descriptor *nd_desc) +{ + struct nd_bus *nd_bus = kzalloc(sizeof(*nd_bus), GFP_KERNEL); + int rc; + + if (!nd_bus) + return NULL; + nd_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL); + if (nd_bus->id < 0) { + kfree(nd_bus); + return NULL; + } + nd_bus->nd_desc = nd_desc; + nd_bus->dev.parent = parent; + nd_bus->dev.release = nd_bus_release; + dev_set_name(&nd_bus->dev, "ndbus%d", nd_bus->id); + rc = device_register(&nd_bus->dev); + if (rc) { + dev_dbg(&nd_bus->dev, "device registration failed: %d\n", rc); + put_device(&nd_bus->dev); + return NULL; + } + + return nd_bus; +} +EXPORT_SYMBOL_GPL(nd_bus_register); + +void nd_bus_unregister(struct nd_bus *nd_bus) +{ + if (!nd_bus) + return; + device_unregister(&nd_bus->dev); +} +EXPORT_SYMBOL_GPL(nd_bus_unregister); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Intel Corporation"); diff --git a/drivers/block/nd/libnd.h b/drivers/block/nd/libnd.h new file mode 100644 index 000000000000..163832937e9c --- /dev/null +++ b/drivers/block/nd/libnd.h @@ -0,0 +1,33 @@ +/* + * libnd - 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 __LIBND_H__ +#define __LIBND_H__ +struct nd_dimm; +struct nd_bus_descriptor; +typedef int (*ndctl_fn)(struct nd_bus_descriptor *nd_desc, + struct nd_dimm *nd_dimm, unsigned int cmd, void *buf, + unsigned int buf_len); + +struct nd_bus_descriptor { + unsigned long dsm_mask; + char *provider_name; + ndctl_fn ndctl; +}; + +struct nd_bus; +struct nd_bus *nd_bus_register(struct device *parent, + struct nd_bus_descriptor *nfit_desc); +void nd_bus_unregister(struct nd_bus *nd_bus); +#endif /* __LIBND_H__ */ diff --git a/drivers/block/nd/nd-private.h b/drivers/block/nd/nd-private.h new file mode 100644 index 000000000000..3dbab29fa0f9 --- /dev/null +++ b/drivers/block/nd/nd-private.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 <linux/device.h> +#include "libnd.h" + +struct nd_bus { + struct nd_bus_descriptor *nd_desc; + struct device dev; + int id; +}; +#endif /* __ND_PRIVATE_H__ */ -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html