18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright(c) 2013-2016 Intel Corporation. All rights reserved. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci#include <linux/memremap.h> 68c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 78c2ecf20Sopenharmony_ci#include <linux/device.h> 88c2ecf20Sopenharmony_ci#include <linux/genhd.h> 98c2ecf20Sopenharmony_ci#include <linux/sizes.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/fs.h> 128c2ecf20Sopenharmony_ci#include <linux/mm.h> 138c2ecf20Sopenharmony_ci#include "nd-core.h" 148c2ecf20Sopenharmony_ci#include "pfn.h" 158c2ecf20Sopenharmony_ci#include "nd.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic void nd_pfn_release(struct device *dev) 188c2ecf20Sopenharmony_ci{ 198c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_nd_region(dev->parent); 208c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn(dev); 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci dev_dbg(dev, "trace\n"); 238c2ecf20Sopenharmony_ci nd_detach_ndns(&nd_pfn->dev, &nd_pfn->ndns); 248c2ecf20Sopenharmony_ci ida_simple_remove(&nd_region->pfn_ida, nd_pfn->id); 258c2ecf20Sopenharmony_ci kfree(nd_pfn->uuid); 268c2ecf20Sopenharmony_ci kfree(nd_pfn); 278c2ecf20Sopenharmony_ci} 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistruct nd_pfn *to_nd_pfn(struct device *dev) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = container_of(dev, struct nd_pfn, dev); 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci WARN_ON(!is_nd_pfn(dev)); 348c2ecf20Sopenharmony_ci return nd_pfn; 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ciEXPORT_SYMBOL(to_nd_pfn); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic ssize_t mode_show(struct device *dev, 398c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci switch (nd_pfn->mode) { 448c2ecf20Sopenharmony_ci case PFN_MODE_RAM: 458c2ecf20Sopenharmony_ci return sprintf(buf, "ram\n"); 468c2ecf20Sopenharmony_ci case PFN_MODE_PMEM: 478c2ecf20Sopenharmony_ci return sprintf(buf, "pmem\n"); 488c2ecf20Sopenharmony_ci default: 498c2ecf20Sopenharmony_ci return sprintf(buf, "none\n"); 508c2ecf20Sopenharmony_ci } 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic ssize_t mode_store(struct device *dev, 548c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t len) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 578c2ecf20Sopenharmony_ci ssize_t rc = 0; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci nd_device_lock(dev); 608c2ecf20Sopenharmony_ci nvdimm_bus_lock(dev); 618c2ecf20Sopenharmony_ci if (dev->driver) 628c2ecf20Sopenharmony_ci rc = -EBUSY; 638c2ecf20Sopenharmony_ci else { 648c2ecf20Sopenharmony_ci size_t n = len - 1; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (strncmp(buf, "pmem\n", n) == 0 678c2ecf20Sopenharmony_ci || strncmp(buf, "pmem", n) == 0) { 688c2ecf20Sopenharmony_ci nd_pfn->mode = PFN_MODE_PMEM; 698c2ecf20Sopenharmony_ci } else if (strncmp(buf, "ram\n", n) == 0 708c2ecf20Sopenharmony_ci || strncmp(buf, "ram", n) == 0) 718c2ecf20Sopenharmony_ci nd_pfn->mode = PFN_MODE_RAM; 728c2ecf20Sopenharmony_ci else if (strncmp(buf, "none\n", n) == 0 738c2ecf20Sopenharmony_ci || strncmp(buf, "none", n) == 0) 748c2ecf20Sopenharmony_ci nd_pfn->mode = PFN_MODE_NONE; 758c2ecf20Sopenharmony_ci else 768c2ecf20Sopenharmony_ci rc = -EINVAL; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci dev_dbg(dev, "result: %zd wrote: %s%s", rc, buf, 798c2ecf20Sopenharmony_ci buf[len - 1] == '\n' ? "" : "\n"); 808c2ecf20Sopenharmony_ci nvdimm_bus_unlock(dev); 818c2ecf20Sopenharmony_ci nd_device_unlock(dev); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return rc ? rc : len; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(mode); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic ssize_t align_show(struct device *dev, 888c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return sprintf(buf, "%ld\n", nd_pfn->align); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic unsigned long *nd_pfn_supported_alignments(unsigned long *alignments) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci alignments[0] = PAGE_SIZE; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (has_transparent_hugepage()) { 1018c2ecf20Sopenharmony_ci alignments[1] = HPAGE_PMD_SIZE; 1028c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD)) 1038c2ecf20Sopenharmony_ci alignments[2] = HPAGE_PUD_SIZE; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci return alignments; 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci/* 1108c2ecf20Sopenharmony_ci * Use pmd mapping if supported as default alignment 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_cistatic unsigned long nd_pfn_default_alignment(void) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (has_transparent_hugepage()) 1168c2ecf20Sopenharmony_ci return HPAGE_PMD_SIZE; 1178c2ecf20Sopenharmony_ci return PAGE_SIZE; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic ssize_t align_store(struct device *dev, 1218c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t len) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1248c2ecf20Sopenharmony_ci unsigned long aligns[MAX_NVDIMM_ALIGN] = { [0] = 0, }; 1258c2ecf20Sopenharmony_ci ssize_t rc; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci nd_device_lock(dev); 1288c2ecf20Sopenharmony_ci nvdimm_bus_lock(dev); 1298c2ecf20Sopenharmony_ci rc = nd_size_select_store(dev, buf, &nd_pfn->align, 1308c2ecf20Sopenharmony_ci nd_pfn_supported_alignments(aligns)); 1318c2ecf20Sopenharmony_ci dev_dbg(dev, "result: %zd wrote: %s%s", rc, buf, 1328c2ecf20Sopenharmony_ci buf[len - 1] == '\n' ? "" : "\n"); 1338c2ecf20Sopenharmony_ci nvdimm_bus_unlock(dev); 1348c2ecf20Sopenharmony_ci nd_device_unlock(dev); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return rc ? rc : len; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(align); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic ssize_t uuid_show(struct device *dev, 1418c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (nd_pfn->uuid) 1468c2ecf20Sopenharmony_ci return sprintf(buf, "%pUb\n", nd_pfn->uuid); 1478c2ecf20Sopenharmony_ci return sprintf(buf, "\n"); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic ssize_t uuid_store(struct device *dev, 1518c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t len) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1548c2ecf20Sopenharmony_ci ssize_t rc; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci nd_device_lock(dev); 1578c2ecf20Sopenharmony_ci rc = nd_uuid_store(dev, &nd_pfn->uuid, buf, len); 1588c2ecf20Sopenharmony_ci dev_dbg(dev, "result: %zd wrote: %s%s", rc, buf, 1598c2ecf20Sopenharmony_ci buf[len - 1] == '\n' ? "" : "\n"); 1608c2ecf20Sopenharmony_ci nd_device_unlock(dev); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return rc ? rc : len; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(uuid); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic ssize_t namespace_show(struct device *dev, 1678c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1708c2ecf20Sopenharmony_ci ssize_t rc; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci nvdimm_bus_lock(dev); 1738c2ecf20Sopenharmony_ci rc = sprintf(buf, "%s\n", nd_pfn->ndns 1748c2ecf20Sopenharmony_ci ? dev_name(&nd_pfn->ndns->dev) : ""); 1758c2ecf20Sopenharmony_ci nvdimm_bus_unlock(dev); 1768c2ecf20Sopenharmony_ci return rc; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic ssize_t namespace_store(struct device *dev, 1808c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t len) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1838c2ecf20Sopenharmony_ci ssize_t rc; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci nd_device_lock(dev); 1868c2ecf20Sopenharmony_ci nvdimm_bus_lock(dev); 1878c2ecf20Sopenharmony_ci rc = nd_namespace_store(dev, &nd_pfn->ndns, buf, len); 1888c2ecf20Sopenharmony_ci dev_dbg(dev, "result: %zd wrote: %s%s", rc, buf, 1898c2ecf20Sopenharmony_ci buf[len - 1] == '\n' ? "" : "\n"); 1908c2ecf20Sopenharmony_ci nvdimm_bus_unlock(dev); 1918c2ecf20Sopenharmony_ci nd_device_unlock(dev); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci return rc; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(namespace); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic ssize_t resource_show(struct device *dev, 1988c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 2018c2ecf20Sopenharmony_ci ssize_t rc; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci nd_device_lock(dev); 2048c2ecf20Sopenharmony_ci if (dev->driver) { 2058c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 2068c2ecf20Sopenharmony_ci u64 offset = __le64_to_cpu(pfn_sb->dataoff); 2078c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 2088c2ecf20Sopenharmony_ci u32 start_pad = __le32_to_cpu(pfn_sb->start_pad); 2098c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci rc = sprintf(buf, "%#llx\n", (unsigned long long) nsio->res.start 2128c2ecf20Sopenharmony_ci + start_pad + offset); 2138c2ecf20Sopenharmony_ci } else { 2148c2ecf20Sopenharmony_ci /* no address to convey if the pfn instance is disabled */ 2158c2ecf20Sopenharmony_ci rc = -ENXIO; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci nd_device_unlock(dev); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci return rc; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_cistatic DEVICE_ATTR_ADMIN_RO(resource); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic ssize_t size_show(struct device *dev, 2248c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 2278c2ecf20Sopenharmony_ci ssize_t rc; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci nd_device_lock(dev); 2308c2ecf20Sopenharmony_ci if (dev->driver) { 2318c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 2328c2ecf20Sopenharmony_ci u64 offset = __le64_to_cpu(pfn_sb->dataoff); 2338c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 2348c2ecf20Sopenharmony_ci u32 start_pad = __le32_to_cpu(pfn_sb->start_pad); 2358c2ecf20Sopenharmony_ci u32 end_trunc = __le32_to_cpu(pfn_sb->end_trunc); 2368c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci rc = sprintf(buf, "%llu\n", (unsigned long long) 2398c2ecf20Sopenharmony_ci resource_size(&nsio->res) - start_pad 2408c2ecf20Sopenharmony_ci - end_trunc - offset); 2418c2ecf20Sopenharmony_ci } else { 2428c2ecf20Sopenharmony_ci /* no size to convey if the pfn instance is disabled */ 2438c2ecf20Sopenharmony_ci rc = -ENXIO; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci nd_device_unlock(dev); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci return rc; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(size); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic ssize_t supported_alignments_show(struct device *dev, 2528c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci unsigned long aligns[MAX_NVDIMM_ALIGN] = { [0] = 0, }; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return nd_size_select_show(0, 2578c2ecf20Sopenharmony_ci nd_pfn_supported_alignments(aligns), buf); 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(supported_alignments); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic struct attribute *nd_pfn_attributes[] = { 2628c2ecf20Sopenharmony_ci &dev_attr_mode.attr, 2638c2ecf20Sopenharmony_ci &dev_attr_namespace.attr, 2648c2ecf20Sopenharmony_ci &dev_attr_uuid.attr, 2658c2ecf20Sopenharmony_ci &dev_attr_align.attr, 2668c2ecf20Sopenharmony_ci &dev_attr_resource.attr, 2678c2ecf20Sopenharmony_ci &dev_attr_size.attr, 2688c2ecf20Sopenharmony_ci &dev_attr_supported_alignments.attr, 2698c2ecf20Sopenharmony_ci NULL, 2708c2ecf20Sopenharmony_ci}; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic struct attribute_group nd_pfn_attribute_group = { 2738c2ecf20Sopenharmony_ci .attrs = nd_pfn_attributes, 2748c2ecf20Sopenharmony_ci}; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ciconst struct attribute_group *nd_pfn_attribute_groups[] = { 2778c2ecf20Sopenharmony_ci &nd_pfn_attribute_group, 2788c2ecf20Sopenharmony_ci &nd_device_attribute_group, 2798c2ecf20Sopenharmony_ci &nd_numa_attribute_group, 2808c2ecf20Sopenharmony_ci NULL, 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic const struct device_type nd_pfn_device_type = { 2848c2ecf20Sopenharmony_ci .name = "nd_pfn", 2858c2ecf20Sopenharmony_ci .release = nd_pfn_release, 2868c2ecf20Sopenharmony_ci .groups = nd_pfn_attribute_groups, 2878c2ecf20Sopenharmony_ci}; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cibool is_nd_pfn(struct device *dev) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci return dev ? dev->type == &nd_pfn_device_type : false; 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ciEXPORT_SYMBOL(is_nd_pfn); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistruct device *nd_pfn_devinit(struct nd_pfn *nd_pfn, 2968c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct device *dev; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci if (!nd_pfn) 3018c2ecf20Sopenharmony_ci return NULL; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci nd_pfn->mode = PFN_MODE_NONE; 3048c2ecf20Sopenharmony_ci nd_pfn->align = nd_pfn_default_alignment(); 3058c2ecf20Sopenharmony_ci dev = &nd_pfn->dev; 3068c2ecf20Sopenharmony_ci device_initialize(&nd_pfn->dev); 3078c2ecf20Sopenharmony_ci if (ndns && !__nd_attach_ndns(&nd_pfn->dev, ndns, &nd_pfn->ndns)) { 3088c2ecf20Sopenharmony_ci dev_dbg(&ndns->dev, "failed, already claimed by %s\n", 3098c2ecf20Sopenharmony_ci dev_name(ndns->claim)); 3108c2ecf20Sopenharmony_ci put_device(dev); 3118c2ecf20Sopenharmony_ci return NULL; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci return dev; 3148c2ecf20Sopenharmony_ci} 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cistatic struct nd_pfn *nd_pfn_alloc(struct nd_region *nd_region) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn; 3198c2ecf20Sopenharmony_ci struct device *dev; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci nd_pfn = kzalloc(sizeof(*nd_pfn), GFP_KERNEL); 3228c2ecf20Sopenharmony_ci if (!nd_pfn) 3238c2ecf20Sopenharmony_ci return NULL; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci nd_pfn->id = ida_simple_get(&nd_region->pfn_ida, 0, 0, GFP_KERNEL); 3268c2ecf20Sopenharmony_ci if (nd_pfn->id < 0) { 3278c2ecf20Sopenharmony_ci kfree(nd_pfn); 3288c2ecf20Sopenharmony_ci return NULL; 3298c2ecf20Sopenharmony_ci } 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci dev = &nd_pfn->dev; 3328c2ecf20Sopenharmony_ci dev_set_name(dev, "pfn%d.%d", nd_region->id, nd_pfn->id); 3338c2ecf20Sopenharmony_ci dev->type = &nd_pfn_device_type; 3348c2ecf20Sopenharmony_ci dev->parent = &nd_region->dev; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci return nd_pfn; 3378c2ecf20Sopenharmony_ci} 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_cistruct device *nd_pfn_create(struct nd_region *nd_region) 3408c2ecf20Sopenharmony_ci{ 3418c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn; 3428c2ecf20Sopenharmony_ci struct device *dev; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci if (!is_memory(&nd_region->dev)) 3458c2ecf20Sopenharmony_ci return NULL; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci nd_pfn = nd_pfn_alloc(nd_region); 3488c2ecf20Sopenharmony_ci dev = nd_pfn_devinit(nd_pfn, NULL); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci __nd_device_register(dev); 3518c2ecf20Sopenharmony_ci return dev; 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci/* 3558c2ecf20Sopenharmony_ci * nd_pfn_clear_memmap_errors() clears any errors in the volatile memmap 3568c2ecf20Sopenharmony_ci * space associated with the namespace. If the memmap is set to DRAM, then 3578c2ecf20Sopenharmony_ci * this is a no-op. Since the memmap area is freshly initialized during 3588c2ecf20Sopenharmony_ci * probe, we have an opportunity to clear any badblocks in this area. 3598c2ecf20Sopenharmony_ci */ 3608c2ecf20Sopenharmony_cistatic int nd_pfn_clear_memmap_errors(struct nd_pfn *nd_pfn) 3618c2ecf20Sopenharmony_ci{ 3628c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_nd_region(nd_pfn->dev.parent); 3638c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 3648c2ecf20Sopenharmony_ci void *zero_page = page_address(ZERO_PAGE(0)); 3658c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 3668c2ecf20Sopenharmony_ci int num_bad, meta_num, rc, bb_present; 3678c2ecf20Sopenharmony_ci sector_t first_bad, meta_start; 3688c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci if (nd_pfn->mode != PFN_MODE_PMEM) 3718c2ecf20Sopenharmony_ci return 0; 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci nsio = to_nd_namespace_io(&ndns->dev); 3748c2ecf20Sopenharmony_ci meta_start = (SZ_4K + sizeof(*pfn_sb)) >> 9; 3758c2ecf20Sopenharmony_ci meta_num = (le64_to_cpu(pfn_sb->dataoff) >> 9) - meta_start; 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci /* 3788c2ecf20Sopenharmony_ci * re-enable the namespace with correct size so that we can access 3798c2ecf20Sopenharmony_ci * the device memmap area. 3808c2ecf20Sopenharmony_ci */ 3818c2ecf20Sopenharmony_ci devm_namespace_disable(&nd_pfn->dev, ndns); 3828c2ecf20Sopenharmony_ci rc = devm_namespace_enable(&nd_pfn->dev, ndns, le64_to_cpu(pfn_sb->dataoff)); 3838c2ecf20Sopenharmony_ci if (rc) 3848c2ecf20Sopenharmony_ci return rc; 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci do { 3878c2ecf20Sopenharmony_ci unsigned long zero_len; 3888c2ecf20Sopenharmony_ci u64 nsoff; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci bb_present = badblocks_check(&nd_region->bb, meta_start, 3918c2ecf20Sopenharmony_ci meta_num, &first_bad, &num_bad); 3928c2ecf20Sopenharmony_ci if (bb_present) { 3938c2ecf20Sopenharmony_ci dev_dbg(&nd_pfn->dev, "meta: %x badblocks at %llx\n", 3948c2ecf20Sopenharmony_ci num_bad, first_bad); 3958c2ecf20Sopenharmony_ci nsoff = ALIGN_DOWN((nd_region->ndr_start 3968c2ecf20Sopenharmony_ci + (first_bad << 9)) - nsio->res.start, 3978c2ecf20Sopenharmony_ci PAGE_SIZE); 3988c2ecf20Sopenharmony_ci zero_len = ALIGN(num_bad << 9, PAGE_SIZE); 3998c2ecf20Sopenharmony_ci while (zero_len) { 4008c2ecf20Sopenharmony_ci unsigned long chunk = min(zero_len, PAGE_SIZE); 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci rc = nvdimm_write_bytes(ndns, nsoff, zero_page, 4038c2ecf20Sopenharmony_ci chunk, 0); 4048c2ecf20Sopenharmony_ci if (rc) 4058c2ecf20Sopenharmony_ci break; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci zero_len -= chunk; 4088c2ecf20Sopenharmony_ci nsoff += chunk; 4098c2ecf20Sopenharmony_ci } 4108c2ecf20Sopenharmony_ci if (rc) { 4118c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, 4128c2ecf20Sopenharmony_ci "error clearing %x badblocks at %llx\n", 4138c2ecf20Sopenharmony_ci num_bad, first_bad); 4148c2ecf20Sopenharmony_ci return rc; 4158c2ecf20Sopenharmony_ci } 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci } while (bb_present); 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci return 0; 4208c2ecf20Sopenharmony_ci} 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_cistatic bool nd_supported_alignment(unsigned long align) 4238c2ecf20Sopenharmony_ci{ 4248c2ecf20Sopenharmony_ci int i; 4258c2ecf20Sopenharmony_ci unsigned long supported[MAX_NVDIMM_ALIGN] = { [0] = 0, }; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (align == 0) 4288c2ecf20Sopenharmony_ci return false; 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci nd_pfn_supported_alignments(supported); 4318c2ecf20Sopenharmony_ci for (i = 0; supported[i]; i++) 4328c2ecf20Sopenharmony_ci if (align == supported[i]) 4338c2ecf20Sopenharmony_ci return true; 4348c2ecf20Sopenharmony_ci return false; 4358c2ecf20Sopenharmony_ci} 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci/** 4388c2ecf20Sopenharmony_ci * nd_pfn_validate - read and validate info-block 4398c2ecf20Sopenharmony_ci * @nd_pfn: fsdax namespace runtime state / properties 4408c2ecf20Sopenharmony_ci * @sig: 'devdax' or 'fsdax' signature 4418c2ecf20Sopenharmony_ci * 4428c2ecf20Sopenharmony_ci * Upon return the info-block buffer contents (->pfn_sb) are 4438c2ecf20Sopenharmony_ci * indeterminate when validation fails, and a coherent info-block 4448c2ecf20Sopenharmony_ci * otherwise. 4458c2ecf20Sopenharmony_ci */ 4468c2ecf20Sopenharmony_ciint nd_pfn_validate(struct nd_pfn *nd_pfn, const char *sig) 4478c2ecf20Sopenharmony_ci{ 4488c2ecf20Sopenharmony_ci u64 checksum, offset; 4498c2ecf20Sopenharmony_ci struct resource *res; 4508c2ecf20Sopenharmony_ci enum nd_pfn_mode mode; 4518c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio; 4528c2ecf20Sopenharmony_ci unsigned long align, start_pad; 4538c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 4548c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 4558c2ecf20Sopenharmony_ci const u8 *parent_uuid = nd_dev_to_uuid(&ndns->dev); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci if (!pfn_sb || !ndns) 4588c2ecf20Sopenharmony_ci return -ENODEV; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci if (!is_memory(nd_pfn->dev.parent)) 4618c2ecf20Sopenharmony_ci return -ENODEV; 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci if (nvdimm_read_bytes(ndns, SZ_4K, pfn_sb, sizeof(*pfn_sb), 0)) 4648c2ecf20Sopenharmony_ci return -ENXIO; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci if (memcmp(pfn_sb->signature, sig, PFN_SIG_LEN) != 0) 4678c2ecf20Sopenharmony_ci return -ENODEV; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci checksum = le64_to_cpu(pfn_sb->checksum); 4708c2ecf20Sopenharmony_ci pfn_sb->checksum = 0; 4718c2ecf20Sopenharmony_ci if (checksum != nd_sb_checksum((struct nd_gen_sb *) pfn_sb)) 4728c2ecf20Sopenharmony_ci return -ENODEV; 4738c2ecf20Sopenharmony_ci pfn_sb->checksum = cpu_to_le64(checksum); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci if (memcmp(pfn_sb->parent_uuid, parent_uuid, 16) != 0) 4768c2ecf20Sopenharmony_ci return -ENODEV; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci if (__le16_to_cpu(pfn_sb->version_minor) < 1) { 4798c2ecf20Sopenharmony_ci pfn_sb->start_pad = 0; 4808c2ecf20Sopenharmony_ci pfn_sb->end_trunc = 0; 4818c2ecf20Sopenharmony_ci } 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci if (__le16_to_cpu(pfn_sb->version_minor) < 2) 4848c2ecf20Sopenharmony_ci pfn_sb->align = 0; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci if (__le16_to_cpu(pfn_sb->version_minor) < 4) { 4878c2ecf20Sopenharmony_ci pfn_sb->page_struct_size = cpu_to_le16(64); 4888c2ecf20Sopenharmony_ci pfn_sb->page_size = cpu_to_le32(PAGE_SIZE); 4898c2ecf20Sopenharmony_ci } 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci switch (le32_to_cpu(pfn_sb->mode)) { 4928c2ecf20Sopenharmony_ci case PFN_MODE_RAM: 4938c2ecf20Sopenharmony_ci case PFN_MODE_PMEM: 4948c2ecf20Sopenharmony_ci break; 4958c2ecf20Sopenharmony_ci default: 4968c2ecf20Sopenharmony_ci return -ENXIO; 4978c2ecf20Sopenharmony_ci } 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci align = le32_to_cpu(pfn_sb->align); 5008c2ecf20Sopenharmony_ci offset = le64_to_cpu(pfn_sb->dataoff); 5018c2ecf20Sopenharmony_ci start_pad = le32_to_cpu(pfn_sb->start_pad); 5028c2ecf20Sopenharmony_ci if (align == 0) 5038c2ecf20Sopenharmony_ci align = 1UL << ilog2(offset); 5048c2ecf20Sopenharmony_ci mode = le32_to_cpu(pfn_sb->mode); 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci if ((le32_to_cpu(pfn_sb->page_size) > PAGE_SIZE) && 5078c2ecf20Sopenharmony_ci (mode == PFN_MODE_PMEM)) { 5088c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, 5098c2ecf20Sopenharmony_ci "init failed, page size mismatch %d\n", 5108c2ecf20Sopenharmony_ci le32_to_cpu(pfn_sb->page_size)); 5118c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5128c2ecf20Sopenharmony_ci } 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci if ((le16_to_cpu(pfn_sb->page_struct_size) < sizeof(struct page)) && 5158c2ecf20Sopenharmony_ci (mode == PFN_MODE_PMEM)) { 5168c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, 5178c2ecf20Sopenharmony_ci "init failed, struct page size mismatch %d\n", 5188c2ecf20Sopenharmony_ci le16_to_cpu(pfn_sb->page_struct_size)); 5198c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci /* 5238c2ecf20Sopenharmony_ci * Check whether the we support the alignment. For Dax if the 5248c2ecf20Sopenharmony_ci * superblock alignment is not matching, we won't initialize 5258c2ecf20Sopenharmony_ci * the device. 5268c2ecf20Sopenharmony_ci */ 5278c2ecf20Sopenharmony_ci if (!nd_supported_alignment(align) && 5288c2ecf20Sopenharmony_ci !memcmp(pfn_sb->signature, DAX_SIG, PFN_SIG_LEN)) { 5298c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "init failed, alignment mismatch: " 5308c2ecf20Sopenharmony_ci "%ld:%ld\n", nd_pfn->align, align); 5318c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5328c2ecf20Sopenharmony_ci } 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci if (!nd_pfn->uuid) { 5358c2ecf20Sopenharmony_ci /* 5368c2ecf20Sopenharmony_ci * When probing a namepace via nd_pfn_probe() the uuid 5378c2ecf20Sopenharmony_ci * is NULL (see: nd_pfn_devinit()) we init settings from 5388c2ecf20Sopenharmony_ci * pfn_sb 5398c2ecf20Sopenharmony_ci */ 5408c2ecf20Sopenharmony_ci nd_pfn->uuid = kmemdup(pfn_sb->uuid, 16, GFP_KERNEL); 5418c2ecf20Sopenharmony_ci if (!nd_pfn->uuid) 5428c2ecf20Sopenharmony_ci return -ENOMEM; 5438c2ecf20Sopenharmony_ci nd_pfn->align = align; 5448c2ecf20Sopenharmony_ci nd_pfn->mode = mode; 5458c2ecf20Sopenharmony_ci } else { 5468c2ecf20Sopenharmony_ci /* 5478c2ecf20Sopenharmony_ci * When probing a pfn / dax instance we validate the 5488c2ecf20Sopenharmony_ci * live settings against the pfn_sb 5498c2ecf20Sopenharmony_ci */ 5508c2ecf20Sopenharmony_ci if (memcmp(nd_pfn->uuid, pfn_sb->uuid, 16) != 0) 5518c2ecf20Sopenharmony_ci return -ENODEV; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci /* 5548c2ecf20Sopenharmony_ci * If the uuid validates, but other settings mismatch 5558c2ecf20Sopenharmony_ci * return EINVAL because userspace has managed to change 5568c2ecf20Sopenharmony_ci * the configuration without specifying new 5578c2ecf20Sopenharmony_ci * identification. 5588c2ecf20Sopenharmony_ci */ 5598c2ecf20Sopenharmony_ci if (nd_pfn->align != align || nd_pfn->mode != mode) { 5608c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, 5618c2ecf20Sopenharmony_ci "init failed, settings mismatch\n"); 5628c2ecf20Sopenharmony_ci dev_dbg(&nd_pfn->dev, "align: %lx:%lx mode: %d:%d\n", 5638c2ecf20Sopenharmony_ci nd_pfn->align, align, nd_pfn->mode, 5648c2ecf20Sopenharmony_ci mode); 5658c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5668c2ecf20Sopenharmony_ci } 5678c2ecf20Sopenharmony_ci } 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci if (align > nvdimm_namespace_capacity(ndns)) { 5708c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "alignment: %lx exceeds capacity %llx\n", 5718c2ecf20Sopenharmony_ci align, nvdimm_namespace_capacity(ndns)); 5728c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5738c2ecf20Sopenharmony_ci } 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_ci /* 5768c2ecf20Sopenharmony_ci * These warnings are verbose because they can only trigger in 5778c2ecf20Sopenharmony_ci * the case where the physical address alignment of the 5788c2ecf20Sopenharmony_ci * namespace has changed since the pfn superblock was 5798c2ecf20Sopenharmony_ci * established. 5808c2ecf20Sopenharmony_ci */ 5818c2ecf20Sopenharmony_ci nsio = to_nd_namespace_io(&ndns->dev); 5828c2ecf20Sopenharmony_ci res = &nsio->res; 5838c2ecf20Sopenharmony_ci if (offset >= resource_size(res)) { 5848c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "pfn array size exceeds capacity of %s\n", 5858c2ecf20Sopenharmony_ci dev_name(&ndns->dev)); 5868c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5878c2ecf20Sopenharmony_ci } 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci if ((align && !IS_ALIGNED(res->start + offset + start_pad, align)) 5908c2ecf20Sopenharmony_ci || !IS_ALIGNED(offset, PAGE_SIZE)) { 5918c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, 5928c2ecf20Sopenharmony_ci "bad offset: %#llx dax disabled align: %#lx\n", 5938c2ecf20Sopenharmony_ci offset, align); 5948c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 5958c2ecf20Sopenharmony_ci } 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci if (!IS_ALIGNED(res->start + le32_to_cpu(pfn_sb->start_pad), 5988c2ecf20Sopenharmony_ci memremap_compat_align())) { 5998c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "resource start misaligned\n"); 6008c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 6018c2ecf20Sopenharmony_ci } 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci if (!IS_ALIGNED(res->end + 1 - le32_to_cpu(pfn_sb->end_trunc), 6048c2ecf20Sopenharmony_ci memremap_compat_align())) { 6058c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "resource end misaligned\n"); 6068c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 6078c2ecf20Sopenharmony_ci } 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci return 0; 6108c2ecf20Sopenharmony_ci} 6118c2ecf20Sopenharmony_ciEXPORT_SYMBOL(nd_pfn_validate); 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ciint nd_pfn_probe(struct device *dev, struct nd_namespace_common *ndns) 6148c2ecf20Sopenharmony_ci{ 6158c2ecf20Sopenharmony_ci int rc; 6168c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn; 6178c2ecf20Sopenharmony_ci struct device *pfn_dev; 6188c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb; 6198c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_nd_region(ndns->dev.parent); 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci if (ndns->force_raw) 6228c2ecf20Sopenharmony_ci return -ENODEV; 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_ci switch (ndns->claim_class) { 6258c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_NONE: 6268c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_PFN: 6278c2ecf20Sopenharmony_ci break; 6288c2ecf20Sopenharmony_ci default: 6298c2ecf20Sopenharmony_ci return -ENODEV; 6308c2ecf20Sopenharmony_ci } 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci nvdimm_bus_lock(&ndns->dev); 6338c2ecf20Sopenharmony_ci nd_pfn = nd_pfn_alloc(nd_region); 6348c2ecf20Sopenharmony_ci pfn_dev = nd_pfn_devinit(nd_pfn, ndns); 6358c2ecf20Sopenharmony_ci nvdimm_bus_unlock(&ndns->dev); 6368c2ecf20Sopenharmony_ci if (!pfn_dev) 6378c2ecf20Sopenharmony_ci return -ENOMEM; 6388c2ecf20Sopenharmony_ci pfn_sb = devm_kmalloc(dev, sizeof(*pfn_sb), GFP_KERNEL); 6398c2ecf20Sopenharmony_ci nd_pfn = to_nd_pfn(pfn_dev); 6408c2ecf20Sopenharmony_ci nd_pfn->pfn_sb = pfn_sb; 6418c2ecf20Sopenharmony_ci rc = nd_pfn_validate(nd_pfn, PFN_SIG); 6428c2ecf20Sopenharmony_ci dev_dbg(dev, "pfn: %s\n", rc == 0 ? dev_name(pfn_dev) : "<none>"); 6438c2ecf20Sopenharmony_ci if (rc < 0) { 6448c2ecf20Sopenharmony_ci nd_detach_ndns(pfn_dev, &nd_pfn->ndns); 6458c2ecf20Sopenharmony_ci put_device(pfn_dev); 6468c2ecf20Sopenharmony_ci } else 6478c2ecf20Sopenharmony_ci __nd_device_register(pfn_dev); 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ci return rc; 6508c2ecf20Sopenharmony_ci} 6518c2ecf20Sopenharmony_ciEXPORT_SYMBOL(nd_pfn_probe); 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_ci/* 6548c2ecf20Sopenharmony_ci * We hotplug memory at sub-section granularity, pad the reserved area 6558c2ecf20Sopenharmony_ci * from the previous section base to the namespace base address. 6568c2ecf20Sopenharmony_ci */ 6578c2ecf20Sopenharmony_cistatic unsigned long init_altmap_base(resource_size_t base) 6588c2ecf20Sopenharmony_ci{ 6598c2ecf20Sopenharmony_ci unsigned long base_pfn = PHYS_PFN(base); 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci return SUBSECTION_ALIGN_DOWN(base_pfn); 6628c2ecf20Sopenharmony_ci} 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_cistatic unsigned long init_altmap_reserve(resource_size_t base) 6658c2ecf20Sopenharmony_ci{ 6668c2ecf20Sopenharmony_ci unsigned long reserve = nd_info_block_reserve() >> PAGE_SHIFT; 6678c2ecf20Sopenharmony_ci unsigned long base_pfn = PHYS_PFN(base); 6688c2ecf20Sopenharmony_ci 6698c2ecf20Sopenharmony_ci reserve += base_pfn - SUBSECTION_ALIGN_DOWN(base_pfn); 6708c2ecf20Sopenharmony_ci return reserve; 6718c2ecf20Sopenharmony_ci} 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_cistatic int __nvdimm_setup_pfn(struct nd_pfn *nd_pfn, struct dev_pagemap *pgmap) 6748c2ecf20Sopenharmony_ci{ 6758c2ecf20Sopenharmony_ci struct range *range = &pgmap->range; 6768c2ecf20Sopenharmony_ci struct vmem_altmap *altmap = &pgmap->altmap; 6778c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 6788c2ecf20Sopenharmony_ci u64 offset = le64_to_cpu(pfn_sb->dataoff); 6798c2ecf20Sopenharmony_ci u32 start_pad = __le32_to_cpu(pfn_sb->start_pad); 6808c2ecf20Sopenharmony_ci u32 end_trunc = __le32_to_cpu(pfn_sb->end_trunc); 6818c2ecf20Sopenharmony_ci u32 reserve = nd_info_block_reserve(); 6828c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 6838c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 6848c2ecf20Sopenharmony_ci resource_size_t base = nsio->res.start + start_pad; 6858c2ecf20Sopenharmony_ci resource_size_t end = nsio->res.end - end_trunc; 6868c2ecf20Sopenharmony_ci struct vmem_altmap __altmap = { 6878c2ecf20Sopenharmony_ci .base_pfn = init_altmap_base(base), 6888c2ecf20Sopenharmony_ci .reserve = init_altmap_reserve(base), 6898c2ecf20Sopenharmony_ci .end_pfn = PHYS_PFN(end), 6908c2ecf20Sopenharmony_ci }; 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci *range = (struct range) { 6938c2ecf20Sopenharmony_ci .start = nsio->res.start + start_pad, 6948c2ecf20Sopenharmony_ci .end = nsio->res.end - end_trunc, 6958c2ecf20Sopenharmony_ci }; 6968c2ecf20Sopenharmony_ci pgmap->nr_range = 1; 6978c2ecf20Sopenharmony_ci if (nd_pfn->mode == PFN_MODE_RAM) { 6988c2ecf20Sopenharmony_ci if (offset < reserve) 6998c2ecf20Sopenharmony_ci return -EINVAL; 7008c2ecf20Sopenharmony_ci nd_pfn->npfns = le64_to_cpu(pfn_sb->npfns); 7018c2ecf20Sopenharmony_ci } else if (nd_pfn->mode == PFN_MODE_PMEM) { 7028c2ecf20Sopenharmony_ci nd_pfn->npfns = PHYS_PFN((range_len(range) - offset)); 7038c2ecf20Sopenharmony_ci if (le64_to_cpu(nd_pfn->pfn_sb->npfns) > nd_pfn->npfns) 7048c2ecf20Sopenharmony_ci dev_info(&nd_pfn->dev, 7058c2ecf20Sopenharmony_ci "number of pfns truncated from %lld to %ld\n", 7068c2ecf20Sopenharmony_ci le64_to_cpu(nd_pfn->pfn_sb->npfns), 7078c2ecf20Sopenharmony_ci nd_pfn->npfns); 7088c2ecf20Sopenharmony_ci memcpy(altmap, &__altmap, sizeof(*altmap)); 7098c2ecf20Sopenharmony_ci altmap->free = PHYS_PFN(offset - reserve); 7108c2ecf20Sopenharmony_ci altmap->alloc = 0; 7118c2ecf20Sopenharmony_ci pgmap->flags |= PGMAP_ALTMAP_VALID; 7128c2ecf20Sopenharmony_ci } else 7138c2ecf20Sopenharmony_ci return -ENXIO; 7148c2ecf20Sopenharmony_ci 7158c2ecf20Sopenharmony_ci return 0; 7168c2ecf20Sopenharmony_ci} 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_cistatic int nd_pfn_init(struct nd_pfn *nd_pfn) 7198c2ecf20Sopenharmony_ci{ 7208c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = nd_pfn->ndns; 7218c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 7228c2ecf20Sopenharmony_ci resource_size_t start, size; 7238c2ecf20Sopenharmony_ci struct nd_region *nd_region; 7248c2ecf20Sopenharmony_ci unsigned long npfns, align; 7258c2ecf20Sopenharmony_ci u32 end_trunc; 7268c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb; 7278c2ecf20Sopenharmony_ci phys_addr_t offset; 7288c2ecf20Sopenharmony_ci const char *sig; 7298c2ecf20Sopenharmony_ci u64 checksum; 7308c2ecf20Sopenharmony_ci int rc; 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci pfn_sb = devm_kmalloc(&nd_pfn->dev, sizeof(*pfn_sb), GFP_KERNEL); 7338c2ecf20Sopenharmony_ci if (!pfn_sb) 7348c2ecf20Sopenharmony_ci return -ENOMEM; 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_ci nd_pfn->pfn_sb = pfn_sb; 7378c2ecf20Sopenharmony_ci if (is_nd_dax(&nd_pfn->dev)) 7388c2ecf20Sopenharmony_ci sig = DAX_SIG; 7398c2ecf20Sopenharmony_ci else 7408c2ecf20Sopenharmony_ci sig = PFN_SIG; 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_ci rc = nd_pfn_validate(nd_pfn, sig); 7438c2ecf20Sopenharmony_ci if (rc == 0) 7448c2ecf20Sopenharmony_ci return nd_pfn_clear_memmap_errors(nd_pfn); 7458c2ecf20Sopenharmony_ci if (rc != -ENODEV) 7468c2ecf20Sopenharmony_ci return rc; 7478c2ecf20Sopenharmony_ci 7488c2ecf20Sopenharmony_ci /* no info block, do init */; 7498c2ecf20Sopenharmony_ci memset(pfn_sb, 0, sizeof(*pfn_sb)); 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci nd_region = to_nd_region(nd_pfn->dev.parent); 7528c2ecf20Sopenharmony_ci if (nd_region->ro) { 7538c2ecf20Sopenharmony_ci dev_info(&nd_pfn->dev, 7548c2ecf20Sopenharmony_ci "%s is read-only, unable to init metadata\n", 7558c2ecf20Sopenharmony_ci dev_name(&nd_region->dev)); 7568c2ecf20Sopenharmony_ci return -ENXIO; 7578c2ecf20Sopenharmony_ci } 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci /* 7608c2ecf20Sopenharmony_ci * Note, we use 64 here for the standard size of struct page, 7618c2ecf20Sopenharmony_ci * debugging options may cause it to be larger in which case the 7628c2ecf20Sopenharmony_ci * implementation will limit the pfns advertised through 7638c2ecf20Sopenharmony_ci * ->direct_access() to those that are included in the memmap. 7648c2ecf20Sopenharmony_ci */ 7658c2ecf20Sopenharmony_ci start = nsio->res.start; 7668c2ecf20Sopenharmony_ci size = resource_size(&nsio->res); 7678c2ecf20Sopenharmony_ci npfns = PHYS_PFN(size - SZ_8K); 7688c2ecf20Sopenharmony_ci align = max(nd_pfn->align, memremap_compat_align()); 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci /* 7718c2ecf20Sopenharmony_ci * When @start is misaligned fail namespace creation. See 7728c2ecf20Sopenharmony_ci * the 'struct nd_pfn_sb' commentary on why ->start_pad is not 7738c2ecf20Sopenharmony_ci * an option. 7748c2ecf20Sopenharmony_ci */ 7758c2ecf20Sopenharmony_ci if (!IS_ALIGNED(start, memremap_compat_align())) { 7768c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "%s: start %pa misaligned to %#lx\n", 7778c2ecf20Sopenharmony_ci dev_name(&ndns->dev), &start, 7788c2ecf20Sopenharmony_ci memremap_compat_align()); 7798c2ecf20Sopenharmony_ci return -EINVAL; 7808c2ecf20Sopenharmony_ci } 7818c2ecf20Sopenharmony_ci end_trunc = start + size - ALIGN_DOWN(start + size, align); 7828c2ecf20Sopenharmony_ci if (nd_pfn->mode == PFN_MODE_PMEM) { 7838c2ecf20Sopenharmony_ci /* 7848c2ecf20Sopenharmony_ci * The altmap should be padded out to the block size used 7858c2ecf20Sopenharmony_ci * when populating the vmemmap. This *should* be equal to 7868c2ecf20Sopenharmony_ci * PMD_SIZE for most architectures. 7878c2ecf20Sopenharmony_ci * 7888c2ecf20Sopenharmony_ci * Also make sure size of struct page is less than 64. We 7898c2ecf20Sopenharmony_ci * want to make sure we use large enough size here so that 7908c2ecf20Sopenharmony_ci * we don't have a dynamic reserve space depending on 7918c2ecf20Sopenharmony_ci * struct page size. But we also want to make sure we notice 7928c2ecf20Sopenharmony_ci * when we end up adding new elements to struct page. 7938c2ecf20Sopenharmony_ci */ 7948c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct page) > MAX_STRUCT_PAGE_SIZE); 7958c2ecf20Sopenharmony_ci offset = ALIGN(start + SZ_8K + MAX_STRUCT_PAGE_SIZE * npfns, align) 7968c2ecf20Sopenharmony_ci - start; 7978c2ecf20Sopenharmony_ci } else if (nd_pfn->mode == PFN_MODE_RAM) 7988c2ecf20Sopenharmony_ci offset = ALIGN(start + SZ_8K, align) - start; 7998c2ecf20Sopenharmony_ci else 8008c2ecf20Sopenharmony_ci return -ENXIO; 8018c2ecf20Sopenharmony_ci 8028c2ecf20Sopenharmony_ci if (offset >= size) { 8038c2ecf20Sopenharmony_ci dev_err(&nd_pfn->dev, "%s unable to satisfy requested alignment\n", 8048c2ecf20Sopenharmony_ci dev_name(&ndns->dev)); 8058c2ecf20Sopenharmony_ci return -ENXIO; 8068c2ecf20Sopenharmony_ci } 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci npfns = PHYS_PFN(size - offset - end_trunc); 8098c2ecf20Sopenharmony_ci pfn_sb->mode = cpu_to_le32(nd_pfn->mode); 8108c2ecf20Sopenharmony_ci pfn_sb->dataoff = cpu_to_le64(offset); 8118c2ecf20Sopenharmony_ci pfn_sb->npfns = cpu_to_le64(npfns); 8128c2ecf20Sopenharmony_ci memcpy(pfn_sb->signature, sig, PFN_SIG_LEN); 8138c2ecf20Sopenharmony_ci memcpy(pfn_sb->uuid, nd_pfn->uuid, 16); 8148c2ecf20Sopenharmony_ci memcpy(pfn_sb->parent_uuid, nd_dev_to_uuid(&ndns->dev), 16); 8158c2ecf20Sopenharmony_ci pfn_sb->version_major = cpu_to_le16(1); 8168c2ecf20Sopenharmony_ci pfn_sb->version_minor = cpu_to_le16(4); 8178c2ecf20Sopenharmony_ci pfn_sb->end_trunc = cpu_to_le32(end_trunc); 8188c2ecf20Sopenharmony_ci pfn_sb->align = cpu_to_le32(nd_pfn->align); 8198c2ecf20Sopenharmony_ci pfn_sb->page_struct_size = cpu_to_le16(MAX_STRUCT_PAGE_SIZE); 8208c2ecf20Sopenharmony_ci pfn_sb->page_size = cpu_to_le32(PAGE_SIZE); 8218c2ecf20Sopenharmony_ci checksum = nd_sb_checksum((struct nd_gen_sb *) pfn_sb); 8228c2ecf20Sopenharmony_ci pfn_sb->checksum = cpu_to_le64(checksum); 8238c2ecf20Sopenharmony_ci 8248c2ecf20Sopenharmony_ci rc = nd_pfn_clear_memmap_errors(nd_pfn); 8258c2ecf20Sopenharmony_ci if (rc) 8268c2ecf20Sopenharmony_ci return rc; 8278c2ecf20Sopenharmony_ci 8288c2ecf20Sopenharmony_ci return nvdimm_write_bytes(ndns, SZ_4K, pfn_sb, sizeof(*pfn_sb), 0); 8298c2ecf20Sopenharmony_ci} 8308c2ecf20Sopenharmony_ci 8318c2ecf20Sopenharmony_ci/* 8328c2ecf20Sopenharmony_ci * Determine the effective resource range and vmem_altmap from an nd_pfn 8338c2ecf20Sopenharmony_ci * instance. 8348c2ecf20Sopenharmony_ci */ 8358c2ecf20Sopenharmony_ciint nvdimm_setup_pfn(struct nd_pfn *nd_pfn, struct dev_pagemap *pgmap) 8368c2ecf20Sopenharmony_ci{ 8378c2ecf20Sopenharmony_ci int rc; 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_ci if (!nd_pfn->uuid || !nd_pfn->ndns) 8408c2ecf20Sopenharmony_ci return -ENODEV; 8418c2ecf20Sopenharmony_ci 8428c2ecf20Sopenharmony_ci rc = nd_pfn_init(nd_pfn); 8438c2ecf20Sopenharmony_ci if (rc) 8448c2ecf20Sopenharmony_ci return rc; 8458c2ecf20Sopenharmony_ci 8468c2ecf20Sopenharmony_ci /* we need a valid pfn_sb before we can init a dev_pagemap */ 8478c2ecf20Sopenharmony_ci return __nvdimm_setup_pfn(nd_pfn, pgmap); 8488c2ecf20Sopenharmony_ci} 8498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nvdimm_setup_pfn); 850