From patchwork Wed May 27 22:25:04 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Williams X-Patchwork-Id: 6494721 Return-Path: X-Original-To: patchwork-linux-nvdimm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 07EC1C0020 for ; Wed, 27 May 2015 22:27:50 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 97212206AB for ; Wed, 27 May 2015 22:27:48 +0000 (UTC) Received: from ml01.01.org (ml01.01.org [198.145.21.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 2E673206DB for ; Wed, 27 May 2015 22:27:47 +0000 (UTC) Received: from ml01.vlan14.01.org (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id 20CD5182845; Wed, 27 May 2015 15:27:47 -0700 (PDT) X-Original-To: linux-nvdimm@lists.01.org Delivered-To: linux-nvdimm@lists.01.org Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by ml01.01.org (Postfix) with ESMTP id 37AA818283D for ; Wed, 27 May 2015 15:27:45 -0700 (PDT) Received: from orsmga001.jf.intel.com ([10.7.209.18]) by fmsmga101.fm.intel.com with ESMTP; 27 May 2015 15:27:44 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.13,507,1427785200"; d="scan'208";a="701259361" Received: from dwillia2-desk3.jf.intel.com (HELO dwillia2-desk3.amr.corp.intel.com) ([10.23.232.36]) by orsmga001.jf.intel.com with ESMTP; 27 May 2015 15:27:44 -0700 Subject: [PATCH v4 04/21] libnd, nfit: dimm/memory-devices From: Dan Williams To: axboe@kernel.dk Date: Wed, 27 May 2015 18:25:04 -0400 Message-ID: <20150527222504.17965.75016.stgit@dwillia2-desk3.amr.corp.intel.com> In-Reply-To: <20150527210155.17965.74864.stgit@dwillia2-desk3.amr.corp.intel.com> References: <20150527210155.17965.74864.stgit@dwillia2-desk3.amr.corp.intel.com> User-Agent: StGit/0.17.1-8-g92dd MIME-Version: 1.0 Cc: sfr@canb.auug.org.au, linux-nvdimm@lists.01.org, neilb@suse.de, gregkh@linuxfoundation.org, "Rafael J. Wysocki" , Robert Moore , linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org, akpm@linux-foundation.org, hch@lst.de, mingo@kernel.org X-BeenThere: linux-nvdimm@lists.01.org X-Mailman-Version: 2.1.17 Precedence: list List-Id: "Linux-nvdimm developer list." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: linux-nvdimm-bounces@lists.01.org Sender: "Linux-nvdimm" X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Enable dimm devices to be registered on a libnd bus. The kernel assigned device id for dimms is dynamic. If userspace needs a more static identifier it should consult a provider-specific attribute. In the case where NFIT is the provider, the 'nmemX/nfit/handle' or 'nmemX/nfit/serial' attributes may be used for this purpose. Cc: Neil Brown Cc: Cc: Greg KH Cc: Robert Moore Cc: Rafael J. Wysocki Signed-off-by: Dan Williams --- drivers/acpi/nfit.c | 160 +++++++++++++++++++++++++++++++++++++++++ drivers/acpi/nfit.h | 1 drivers/block/nd/Makefile | 1 drivers/block/nd/bus.c | 14 +++- drivers/block/nd/core.c | 29 +++++++ drivers/block/nd/dimm_devs.c | 92 ++++++++++++++++++++++++ drivers/block/nd/nd-private.h | 12 +++ include/linux/libnd.h | 11 +++ 8 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 drivers/block/nd/dimm_devs.c diff --git a/drivers/acpi/nfit.c b/drivers/acpi/nfit.c index d31a0fffafcc..b26e1a4a59e3 100644 --- a/drivers/acpi/nfit.c +++ b/drivers/acpi/nfit.c @@ -344,6 +344,164 @@ const struct attribute_group *acpi_nfit_attribute_groups[] = { }; EXPORT_SYMBOL_GPL(acpi_nfit_attribute_groups); +static struct acpi_nfit_memory_map *to_nfit_memdev(struct device *dev) +{ + struct nd_dimm *nd_dimm = to_nd_dimm(dev); + struct nfit_mem *nfit_mem = nd_dimm_provider_data(nd_dimm); + + return __to_nfit_memdev(nfit_mem); +} + +static struct acpi_nfit_control_region *to_nfit_dcr(struct device *dev) +{ + struct nd_dimm *nd_dimm = to_nd_dimm(dev); + struct nfit_mem *nfit_mem = nd_dimm_provider_data(nd_dimm); + + return nfit_mem->dcr; +} + +static ssize_t handle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_memory_map *memdev = to_nfit_memdev(dev); + + return sprintf(buf, "%#x\n", memdev->device_handle); +} +static DEVICE_ATTR_RO(handle); + +static ssize_t phys_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_memory_map *memdev = to_nfit_memdev(dev); + + return sprintf(buf, "%#x\n", memdev->physical_id); +} +static DEVICE_ATTR_RO(phys_id); + +static ssize_t vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev); + + return sprintf(buf, "%#x\n", dcr->vendor_id); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t rev_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev); + + return sprintf(buf, "%#x\n", dcr->revision_id); +} +static DEVICE_ATTR_RO(rev_id); + +static ssize_t device_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev); + + return sprintf(buf, "%#x\n", dcr->device_id); +} +static DEVICE_ATTR_RO(device); + +static ssize_t format_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev); + + return sprintf(buf, "%#x\n", dcr->code); +} +static DEVICE_ATTR_RO(format); + +static ssize_t serial_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev); + + return sprintf(buf, "%#x\n", dcr->serial_number); +} +static DEVICE_ATTR_RO(serial); + +static struct attribute *acpi_nfit_dimm_attributes[] = { + &dev_attr_handle.attr, + &dev_attr_phys_id.attr, + &dev_attr_vendor.attr, + &dev_attr_device.attr, + &dev_attr_format.attr, + &dev_attr_serial.attr, + &dev_attr_rev_id.attr, + NULL, +}; + +static umode_t acpi_nfit_dimm_attr_visible(struct kobject *kobj, struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + + if (to_nfit_dcr(dev)) + return a->mode; + else + return 0; +} + +static struct attribute_group acpi_nfit_dimm_attribute_group = { + .name = "nfit", + .attrs = acpi_nfit_dimm_attributes, + .is_visible = acpi_nfit_dimm_attr_visible, +}; + +static const struct attribute_group *acpi_nfit_dimm_attribute_groups[] = { + &acpi_nfit_dimm_attribute_group, + NULL, +}; + +static struct nd_dimm *acpi_nfit_dimm_by_handle(struct acpi_nfit_desc *acpi_desc, + u32 device_handle) +{ + struct nfit_mem *nfit_mem; + + list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) + if (__to_nfit_memdev(nfit_mem)->device_handle == device_handle) + return nfit_mem->nd_dimm; + + return NULL; +} + +static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc) +{ + struct nfit_mem *nfit_mem; + + list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) { + struct nd_dimm *nd_dimm; + unsigned long flags = 0; + u32 device_handle; + + device_handle = __to_nfit_memdev(nfit_mem)->device_handle; + nd_dimm = acpi_nfit_dimm_by_handle(acpi_desc, device_handle); + if (nd_dimm) { + /* + * If for some reason we find multiple DCRs the + * first one wins + */ + dev_err(acpi_desc->dev, "duplicate DCR detected: %s\n", + nd_dimm_name(nd_dimm)); + continue; + } + + if (nfit_mem->bdw && nfit_mem->memdev_pmem) + flags |= NDD_ALIASING; + + nd_dimm = nd_dimm_create(acpi_desc->nd_bus, nfit_mem, + acpi_nfit_dimm_attribute_groups, flags); + if (!nd_dimm) + return -ENOMEM; + + nfit_mem->nd_dimm = nd_dimm; + } + + return 0; +} + static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz) { struct device *dev = acpi_desc->dev; @@ -371,7 +529,7 @@ static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz) if (nfit_mem_init(acpi_desc) != 0) return -ENOMEM; - return 0; + return acpi_nfit_register_dimms(acpi_desc); } static int acpi_nfit_add(struct acpi_device *adev) diff --git a/drivers/acpi/nfit.h b/drivers/acpi/nfit.h index b6c85d773ca1..9d4c1634cb0e 100644 --- a/drivers/acpi/nfit.h +++ b/drivers/acpi/nfit.h @@ -59,6 +59,7 @@ struct nfit_memdev { /* assembled tables for a given dimm/memory-device */ struct nfit_mem { + struct nd_dimm *nd_dimm; struct acpi_nfit_memory_map *memdev_dcr; struct acpi_nfit_memory_map *memdev_pmem; struct acpi_nfit_control_region *dcr; diff --git a/drivers/block/nd/Makefile b/drivers/block/nd/Makefile index 34d1b58b3258..2954b9543bec 100644 --- a/drivers/block/nd/Makefile +++ b/drivers/block/nd/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_LIBND) += libnd.o libnd-y := core.o libnd-y += bus.o +libnd-y += dimm_devs.o diff --git a/drivers/block/nd/bus.c b/drivers/block/nd/bus.c index 635f2e926426..ee56aa1ab2ad 100644 --- a/drivers/block/nd/bus.c +++ b/drivers/block/nd/bus.c @@ -13,6 +13,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include +#include #include #include #include @@ -21,6 +22,10 @@ static int nd_bus_major; static struct class *nd_class; +struct bus_type nd_bus_type = { + .name = "nd", +}; + int nd_bus_create_ndctl(struct nd_bus *nd_bus) { dev_t devt = MKDEV(nd_bus_major, nd_bus->id); @@ -59,9 +64,13 @@ int __init nd_bus_init(void) { int rc; + rc = bus_register(&nd_bus_type); + if (rc) + return rc; + rc = register_chrdev(0, "ndctl", &nd_bus_fops); if (rc < 0) - return rc; + goto err_chrdev; nd_bus_major = rc; nd_class = class_create(THIS_MODULE, "nd"); @@ -72,6 +81,8 @@ int __init nd_bus_init(void) err_class: unregister_chrdev(nd_bus_major, "ndctl"); + err_chrdev: + bus_unregister(&nd_bus_type); return rc; } @@ -80,4 +91,5 @@ void __exit nd_bus_exit(void) { class_destroy(nd_class); unregister_chrdev(nd_bus_major, "ndctl"); + bus_unregister(&nd_bus_type); } diff --git a/drivers/block/nd/core.c b/drivers/block/nd/core.c index 49b7ac8f7606..4d0e53ecdcb0 100644 --- a/drivers/block/nd/core.c +++ b/drivers/block/nd/core.c @@ -46,6 +46,19 @@ struct nd_bus_descriptor *to_nd_desc(struct nd_bus *nd_bus) } EXPORT_SYMBOL_GPL(to_nd_desc); +struct nd_bus *walk_to_nd_bus(struct device *nd_dev) +{ + struct device *dev; + + for (dev = nd_dev; dev; dev = dev->parent) + if (dev->release == nd_bus_release) + break; + dev_WARN_ONCE(nd_dev, !dev, "invalid dev, not on nd bus\n"); + if (dev) + return to_nd_bus(dev); + return NULL; +} + static const char *nd_bus_provider(struct nd_bus *nd_bus) { struct nd_bus_descriptor *nd_desc = nd_bus->nd_desc; @@ -118,6 +131,21 @@ struct nd_bus *nd_bus_register(struct device *parent, } EXPORT_SYMBOL_GPL(nd_bus_register); +static int child_unregister(struct device *dev, void *data) +{ + /* + * the singular ndctl class device per bus needs to be + * "device_destroy"ed, so skip it here + * + * i.e. remove classless children + */ + if (dev->class) + /* pass */; + else + device_unregister(dev); + return 0; +} + void nd_bus_unregister(struct nd_bus *nd_bus) { if (!nd_bus) @@ -127,6 +155,7 @@ void nd_bus_unregister(struct nd_bus *nd_bus) list_del_init(&nd_bus->list); mutex_unlock(&nd_bus_list_mutex); + device_for_each_child(&nd_bus->dev, NULL, child_unregister); nd_bus_destroy_ndctl(nd_bus); device_unregister(&nd_bus->dev); diff --git a/drivers/block/nd/dimm_devs.c b/drivers/block/nd/dimm_devs.c new file mode 100644 index 000000000000..19b081392f2f --- /dev/null +++ b/drivers/block/nd/dimm_devs.c @@ -0,0 +1,92 @@ +/* + * 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. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include "nd-private.h" + +static DEFINE_IDA(dimm_ida); + +static void nd_dimm_release(struct device *dev) +{ + struct nd_dimm *nd_dimm = to_nd_dimm(dev); + + ida_simple_remove(&dimm_ida, nd_dimm->id); + kfree(nd_dimm); +} + +static struct device_type nd_dimm_device_type = { + .name = "nd_dimm", + .release = nd_dimm_release, +}; + +static bool is_nd_dimm(struct device *dev) +{ + return dev->type == &nd_dimm_device_type; +} + +struct nd_dimm *to_nd_dimm(struct device *dev) +{ + struct nd_dimm *nd_dimm = container_of(dev, struct nd_dimm, dev); + + WARN_ON(!is_nd_dimm(dev)); + return nd_dimm; +} +EXPORT_SYMBOL_GPL(to_nd_dimm); + +const char *nd_dimm_name(struct nd_dimm *nd_dimm) +{ + return dev_name(&nd_dimm->dev); +} +EXPORT_SYMBOL_GPL(nd_dimm_name); + +void *nd_dimm_provider_data(struct nd_dimm *nd_dimm) +{ + return nd_dimm->provider_data; +} +EXPORT_SYMBOL_GPL(nd_dimm_provider_data); + +struct nd_dimm *nd_dimm_create(struct nd_bus *nd_bus, void *provider_data, + const struct attribute_group **groups, unsigned long flags) +{ + struct nd_dimm *nd_dimm = kzalloc(sizeof(*nd_dimm), GFP_KERNEL); + struct device *dev; + + if (!nd_dimm) + return NULL; + + nd_dimm->id = ida_simple_get(&dimm_ida, 0, 0, GFP_KERNEL); + if (nd_dimm->id < 0) { + kfree(nd_dimm); + return NULL; + } + nd_dimm->provider_data = provider_data; + nd_dimm->flags = flags; + + dev = &nd_dimm->dev; + dev_set_name(dev, "nmem%d", nd_dimm->id); + dev->parent = &nd_bus->dev; + dev->type = &nd_dimm_device_type; + dev->bus = &nd_bus_type; + dev->groups = groups; + if (device_register(dev) != 0) { + put_device(dev); + return NULL; + } + + return nd_dimm; +} +EXPORT_SYMBOL_GPL(nd_dimm_create); diff --git a/drivers/block/nd/nd-private.h b/drivers/block/nd/nd-private.h index 884601f65a15..251ecdd77153 100644 --- a/drivers/block/nd/nd-private.h +++ b/drivers/block/nd/nd-private.h @@ -15,6 +15,10 @@ #include #include +extern struct list_head nd_bus_list; +extern struct mutex nd_bus_list_mutex; +extern struct bus_type nd_bus_type; + struct nd_bus { struct nd_bus_descriptor *nd_desc; struct list_head list; @@ -22,6 +26,14 @@ struct nd_bus { int id; }; +struct nd_dimm { + unsigned long flags; + void *provider_data; + struct device dev; + int id; +}; + +struct nd_bus *walk_to_nd_bus(struct device *nd_dev); int __init nd_bus_init(void); void __exit nd_bus_exit(void); int nd_bus_create_ndctl(struct nd_bus *nd_bus); diff --git a/include/linux/libnd.h b/include/linux/libnd.h index 04a97653d56c..76d5839fb50e 100644 --- a/include/linux/libnd.h +++ b/include/linux/libnd.h @@ -14,6 +14,12 @@ */ #ifndef __LIBND_H__ #define __LIBND_H__ + +enum { + /* when a dimm supports both PMEM and BLK access a label is required */ + NDD_ALIASING = 1 << 0, +}; + extern struct attribute_group nd_bus_attribute_group; struct nd_dimm; @@ -35,5 +41,10 @@ struct nd_bus *nd_bus_register(struct device *parent, struct nd_bus_descriptor *nfit_desc); void nd_bus_unregister(struct nd_bus *nd_bus); struct nd_bus *to_nd_bus(struct device *dev); +struct nd_dimm *to_nd_dimm(struct device *dev); struct nd_bus_descriptor *to_nd_desc(struct nd_bus *nd_bus); +const char *nd_dimm_name(struct nd_dimm *nd_dimm); +void *nd_dimm_provider_data(struct nd_dimm *nd_dimm); +struct nd_dimm *nd_dimm_create(struct nd_bus *nd_bus, void *provider_data, + const struct attribute_group **groups, unsigned long flags); #endif /* __LIBND_H__ */