18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci#include <linux/device.h> 68c2ecf20Sopenharmony_ci#include <linux/sizes.h> 78c2ecf20Sopenharmony_ci#include "nd-core.h" 88c2ecf20Sopenharmony_ci#include "pmem.h" 98c2ecf20Sopenharmony_ci#include "pfn.h" 108c2ecf20Sopenharmony_ci#include "btt.h" 118c2ecf20Sopenharmony_ci#include "nd.h" 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_civoid __nd_detach_ndns(struct device *dev, struct nd_namespace_common **_ndns) 148c2ecf20Sopenharmony_ci{ 158c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = *_ndns; 168c2ecf20Sopenharmony_ci struct nvdimm_bus *nvdimm_bus; 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci if (!ndns) 198c2ecf20Sopenharmony_ci return; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci nvdimm_bus = walk_to_nvdimm_bus(&ndns->dev); 228c2ecf20Sopenharmony_ci lockdep_assert_held(&nvdimm_bus->reconfig_mutex); 238c2ecf20Sopenharmony_ci dev_WARN_ONCE(dev, ndns->claim != dev, "%s: invalid claim\n", __func__); 248c2ecf20Sopenharmony_ci ndns->claim = NULL; 258c2ecf20Sopenharmony_ci *_ndns = NULL; 268c2ecf20Sopenharmony_ci put_device(&ndns->dev); 278c2ecf20Sopenharmony_ci} 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_civoid nd_detach_ndns(struct device *dev, 308c2ecf20Sopenharmony_ci struct nd_namespace_common **_ndns) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = *_ndns; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci if (!ndns) 358c2ecf20Sopenharmony_ci return; 368c2ecf20Sopenharmony_ci get_device(&ndns->dev); 378c2ecf20Sopenharmony_ci nvdimm_bus_lock(&ndns->dev); 388c2ecf20Sopenharmony_ci __nd_detach_ndns(dev, _ndns); 398c2ecf20Sopenharmony_ci nvdimm_bus_unlock(&ndns->dev); 408c2ecf20Sopenharmony_ci put_device(&ndns->dev); 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cibool __nd_attach_ndns(struct device *dev, struct nd_namespace_common *attach, 448c2ecf20Sopenharmony_ci struct nd_namespace_common **_ndns) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&attach->dev); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (attach->claim) 498c2ecf20Sopenharmony_ci return false; 508c2ecf20Sopenharmony_ci lockdep_assert_held(&nvdimm_bus->reconfig_mutex); 518c2ecf20Sopenharmony_ci dev_WARN_ONCE(dev, *_ndns, "%s: invalid claim\n", __func__); 528c2ecf20Sopenharmony_ci attach->claim = dev; 538c2ecf20Sopenharmony_ci *_ndns = attach; 548c2ecf20Sopenharmony_ci get_device(&attach->dev); 558c2ecf20Sopenharmony_ci return true; 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cibool nd_attach_ndns(struct device *dev, struct nd_namespace_common *attach, 598c2ecf20Sopenharmony_ci struct nd_namespace_common **_ndns) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci bool claimed; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci nvdimm_bus_lock(&attach->dev); 648c2ecf20Sopenharmony_ci claimed = __nd_attach_ndns(dev, attach, _ndns); 658c2ecf20Sopenharmony_ci nvdimm_bus_unlock(&attach->dev); 668c2ecf20Sopenharmony_ci return claimed; 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int namespace_match(struct device *dev, void *data) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci char *name = data; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci return strcmp(name, dev_name(dev)) == 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic bool is_idle(struct device *dev, struct nd_namespace_common *ndns) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_nd_region(dev->parent); 798c2ecf20Sopenharmony_ci struct device *seed = NULL; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci if (is_nd_btt(dev)) 828c2ecf20Sopenharmony_ci seed = nd_region->btt_seed; 838c2ecf20Sopenharmony_ci else if (is_nd_pfn(dev)) 848c2ecf20Sopenharmony_ci seed = nd_region->pfn_seed; 858c2ecf20Sopenharmony_ci else if (is_nd_dax(dev)) 868c2ecf20Sopenharmony_ci seed = nd_region->dax_seed; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if (seed == dev || ndns || dev->driver) 898c2ecf20Sopenharmony_ci return false; 908c2ecf20Sopenharmony_ci return true; 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistruct nd_pfn *to_nd_pfn_safe(struct device *dev) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci /* 968c2ecf20Sopenharmony_ci * pfn device attributes are re-used by dax device instances, so we 978c2ecf20Sopenharmony_ci * need to be careful to correct device-to-nd_pfn conversion. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci if (is_nd_pfn(dev)) 1008c2ecf20Sopenharmony_ci return to_nd_pfn(dev); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci if (is_nd_dax(dev)) { 1038c2ecf20Sopenharmony_ci struct nd_dax *nd_dax = to_nd_dax(dev); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci return &nd_dax->nd_pfn; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci WARN_ON(1); 1098c2ecf20Sopenharmony_ci return NULL; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic void nd_detach_and_reset(struct device *dev, 1138c2ecf20Sopenharmony_ci struct nd_namespace_common **_ndns) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci /* detach the namespace and destroy / reset the device */ 1168c2ecf20Sopenharmony_ci __nd_detach_ndns(dev, _ndns); 1178c2ecf20Sopenharmony_ci if (is_idle(dev, *_ndns)) { 1188c2ecf20Sopenharmony_ci nd_device_unregister(dev, ND_ASYNC); 1198c2ecf20Sopenharmony_ci } else if (is_nd_btt(dev)) { 1208c2ecf20Sopenharmony_ci struct nd_btt *nd_btt = to_nd_btt(dev); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci nd_btt->lbasize = 0; 1238c2ecf20Sopenharmony_ci kfree(nd_btt->uuid); 1248c2ecf20Sopenharmony_ci nd_btt->uuid = NULL; 1258c2ecf20Sopenharmony_ci } else if (is_nd_pfn(dev) || is_nd_dax(dev)) { 1268c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn_safe(dev); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci kfree(nd_pfn->uuid); 1298c2ecf20Sopenharmony_ci nd_pfn->uuid = NULL; 1308c2ecf20Sopenharmony_ci nd_pfn->mode = PFN_MODE_NONE; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cissize_t nd_namespace_store(struct device *dev, 1358c2ecf20Sopenharmony_ci struct nd_namespace_common **_ndns, const char *buf, 1368c2ecf20Sopenharmony_ci size_t len) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns; 1398c2ecf20Sopenharmony_ci struct device *found; 1408c2ecf20Sopenharmony_ci char *name; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci if (dev->driver) { 1438c2ecf20Sopenharmony_ci dev_dbg(dev, "namespace already active\n"); 1448c2ecf20Sopenharmony_ci return -EBUSY; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci name = kstrndup(buf, len, GFP_KERNEL); 1488c2ecf20Sopenharmony_ci if (!name) 1498c2ecf20Sopenharmony_ci return -ENOMEM; 1508c2ecf20Sopenharmony_ci strim(name); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (strncmp(name, "namespace", 9) == 0 || strcmp(name, "") == 0) 1538c2ecf20Sopenharmony_ci /* pass */; 1548c2ecf20Sopenharmony_ci else { 1558c2ecf20Sopenharmony_ci len = -EINVAL; 1568c2ecf20Sopenharmony_ci goto out; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci ndns = *_ndns; 1608c2ecf20Sopenharmony_ci if (strcmp(name, "") == 0) { 1618c2ecf20Sopenharmony_ci nd_detach_and_reset(dev, _ndns); 1628c2ecf20Sopenharmony_ci goto out; 1638c2ecf20Sopenharmony_ci } else if (ndns) { 1648c2ecf20Sopenharmony_ci dev_dbg(dev, "namespace already set to: %s\n", 1658c2ecf20Sopenharmony_ci dev_name(&ndns->dev)); 1668c2ecf20Sopenharmony_ci len = -EBUSY; 1678c2ecf20Sopenharmony_ci goto out; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci found = device_find_child(dev->parent, name, namespace_match); 1718c2ecf20Sopenharmony_ci if (!found) { 1728c2ecf20Sopenharmony_ci dev_dbg(dev, "'%s' not found under %s\n", name, 1738c2ecf20Sopenharmony_ci dev_name(dev->parent)); 1748c2ecf20Sopenharmony_ci len = -ENODEV; 1758c2ecf20Sopenharmony_ci goto out; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci ndns = to_ndns(found); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci switch (ndns->claim_class) { 1818c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_NONE: 1828c2ecf20Sopenharmony_ci break; 1838c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_BTT: 1848c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_BTT2: 1858c2ecf20Sopenharmony_ci if (!is_nd_btt(dev)) { 1868c2ecf20Sopenharmony_ci len = -EBUSY; 1878c2ecf20Sopenharmony_ci goto out_attach; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci break; 1908c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_PFN: 1918c2ecf20Sopenharmony_ci if (!is_nd_pfn(dev)) { 1928c2ecf20Sopenharmony_ci len = -EBUSY; 1938c2ecf20Sopenharmony_ci goto out_attach; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci break; 1968c2ecf20Sopenharmony_ci case NVDIMM_CCLASS_DAX: 1978c2ecf20Sopenharmony_ci if (!is_nd_dax(dev)) { 1988c2ecf20Sopenharmony_ci len = -EBUSY; 1998c2ecf20Sopenharmony_ci goto out_attach; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci break; 2028c2ecf20Sopenharmony_ci default: 2038c2ecf20Sopenharmony_ci len = -EBUSY; 2048c2ecf20Sopenharmony_ci goto out_attach; 2058c2ecf20Sopenharmony_ci break; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (__nvdimm_namespace_capacity(ndns) < SZ_16M) { 2098c2ecf20Sopenharmony_ci dev_dbg(dev, "%s too small to host\n", name); 2108c2ecf20Sopenharmony_ci len = -ENXIO; 2118c2ecf20Sopenharmony_ci goto out_attach; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci WARN_ON_ONCE(!is_nvdimm_bus_locked(dev)); 2158c2ecf20Sopenharmony_ci if (!__nd_attach_ndns(dev, ndns, _ndns)) { 2168c2ecf20Sopenharmony_ci dev_dbg(dev, "%s already claimed\n", 2178c2ecf20Sopenharmony_ci dev_name(&ndns->dev)); 2188c2ecf20Sopenharmony_ci len = -EBUSY; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci out_attach: 2228c2ecf20Sopenharmony_ci put_device(&ndns->dev); /* from device_find_child */ 2238c2ecf20Sopenharmony_ci out: 2248c2ecf20Sopenharmony_ci kfree(name); 2258c2ecf20Sopenharmony_ci return len; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci/* 2298c2ecf20Sopenharmony_ci * nd_sb_checksum: compute checksum for a generic info block 2308c2ecf20Sopenharmony_ci * 2318c2ecf20Sopenharmony_ci * Returns a fletcher64 checksum of everything in the given info block 2328c2ecf20Sopenharmony_ci * except the last field (since that's where the checksum lives). 2338c2ecf20Sopenharmony_ci */ 2348c2ecf20Sopenharmony_ciu64 nd_sb_checksum(struct nd_gen_sb *nd_gen_sb) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci u64 sum; 2378c2ecf20Sopenharmony_ci __le64 sum_save; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct btt_sb) != SZ_4K); 2408c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct nd_pfn_sb) != SZ_4K); 2418c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct nd_gen_sb) != SZ_4K); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci sum_save = nd_gen_sb->checksum; 2448c2ecf20Sopenharmony_ci nd_gen_sb->checksum = 0; 2458c2ecf20Sopenharmony_ci sum = nd_fletcher64(nd_gen_sb, sizeof(*nd_gen_sb), 1); 2468c2ecf20Sopenharmony_ci nd_gen_sb->checksum = sum_save; 2478c2ecf20Sopenharmony_ci return sum; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ciEXPORT_SYMBOL(nd_sb_checksum); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic int nsio_rw_bytes(struct nd_namespace_common *ndns, 2528c2ecf20Sopenharmony_ci resource_size_t offset, void *buf, size_t size, int rw, 2538c2ecf20Sopenharmony_ci unsigned long flags) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 2568c2ecf20Sopenharmony_ci unsigned int sz_align = ALIGN(size + (offset & (512 - 1)), 512); 2578c2ecf20Sopenharmony_ci sector_t sector = offset >> 9; 2588c2ecf20Sopenharmony_ci int rc = 0, ret = 0; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci if (unlikely(!size)) 2618c2ecf20Sopenharmony_ci return 0; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci if (unlikely(offset + size > nsio->size)) { 2648c2ecf20Sopenharmony_ci dev_WARN_ONCE(&ndns->dev, 1, "request out of range\n"); 2658c2ecf20Sopenharmony_ci return -EFAULT; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (rw == READ) { 2698c2ecf20Sopenharmony_ci if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align))) 2708c2ecf20Sopenharmony_ci return -EIO; 2718c2ecf20Sopenharmony_ci if (copy_mc_to_kernel(buf, nsio->addr + offset, size) != 0) 2728c2ecf20Sopenharmony_ci return -EIO; 2738c2ecf20Sopenharmony_ci return 0; 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci if (unlikely(is_bad_pmem(&nsio->bb, sector, sz_align))) { 2778c2ecf20Sopenharmony_ci if (IS_ALIGNED(offset, 512) && IS_ALIGNED(size, 512) 2788c2ecf20Sopenharmony_ci && !(flags & NVDIMM_IO_ATOMIC)) { 2798c2ecf20Sopenharmony_ci long cleared; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci might_sleep(); 2828c2ecf20Sopenharmony_ci cleared = nvdimm_clear_poison(&ndns->dev, 2838c2ecf20Sopenharmony_ci nsio->res.start + offset, size); 2848c2ecf20Sopenharmony_ci if (cleared < size) 2858c2ecf20Sopenharmony_ci rc = -EIO; 2868c2ecf20Sopenharmony_ci if (cleared > 0 && cleared / 512) { 2878c2ecf20Sopenharmony_ci cleared /= 512; 2888c2ecf20Sopenharmony_ci badblocks_clear(&nsio->bb, sector, cleared); 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci arch_invalidate_pmem(nsio->addr + offset, size); 2918c2ecf20Sopenharmony_ci } else 2928c2ecf20Sopenharmony_ci rc = -EIO; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci memcpy_flushcache(nsio->addr + offset, buf, size); 2968c2ecf20Sopenharmony_ci ret = nvdimm_flush(to_nd_region(ndns->dev.parent), NULL); 2978c2ecf20Sopenharmony_ci if (ret) 2988c2ecf20Sopenharmony_ci rc = ret; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci return rc; 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ciint devm_nsio_enable(struct device *dev, struct nd_namespace_io *nsio, 3048c2ecf20Sopenharmony_ci resource_size_t size) 3058c2ecf20Sopenharmony_ci{ 3068c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns = &nsio->common; 3078c2ecf20Sopenharmony_ci struct range range = { 3088c2ecf20Sopenharmony_ci .start = nsio->res.start, 3098c2ecf20Sopenharmony_ci .end = nsio->res.end, 3108c2ecf20Sopenharmony_ci }; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci nsio->size = size; 3138c2ecf20Sopenharmony_ci if (!devm_request_mem_region(dev, range.start, size, 3148c2ecf20Sopenharmony_ci dev_name(&ndns->dev))) { 3158c2ecf20Sopenharmony_ci dev_warn(dev, "could not reserve region %pR\n", &nsio->res); 3168c2ecf20Sopenharmony_ci return -EBUSY; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci ndns->rw_bytes = nsio_rw_bytes; 3208c2ecf20Sopenharmony_ci if (devm_init_badblocks(dev, &nsio->bb)) 3218c2ecf20Sopenharmony_ci return -ENOMEM; 3228c2ecf20Sopenharmony_ci nvdimm_badblocks_populate(to_nd_region(ndns->dev.parent), &nsio->bb, 3238c2ecf20Sopenharmony_ci &range); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci nsio->addr = devm_memremap(dev, range.start, size, ARCH_MEMREMAP_PMEM); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(nsio->addr); 3288c2ecf20Sopenharmony_ci} 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_civoid devm_nsio_disable(struct device *dev, struct nd_namespace_io *nsio) 3318c2ecf20Sopenharmony_ci{ 3328c2ecf20Sopenharmony_ci struct resource *res = &nsio->res; 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci devm_memunmap(dev, nsio->addr); 3358c2ecf20Sopenharmony_ci devm_exit_badblocks(dev, &nsio->bb); 3368c2ecf20Sopenharmony_ci devm_release_mem_region(dev, res->start, nsio->size); 3378c2ecf20Sopenharmony_ci} 338