18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Persistent Memory Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2014-2015, Intel Corporation. 68c2ecf20Sopenharmony_ci * Copyright (c) 2015, Christoph Hellwig <hch@lst.de>. 78c2ecf20Sopenharmony_ci * Copyright (c) 2015, Boaz Harrosh <boaz@plexistor.com>. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 118c2ecf20Sopenharmony_ci#include <linux/hdreg.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/set_memory.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 178c2ecf20Sopenharmony_ci#include <linux/badblocks.h> 188c2ecf20Sopenharmony_ci#include <linux/memremap.h> 198c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 208c2ecf20Sopenharmony_ci#include <linux/blk-mq.h> 218c2ecf20Sopenharmony_ci#include <linux/pfn_t.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci#include <linux/uio.h> 248c2ecf20Sopenharmony_ci#include <linux/dax.h> 258c2ecf20Sopenharmony_ci#include <linux/nd.h> 268c2ecf20Sopenharmony_ci#include <linux/backing-dev.h> 278c2ecf20Sopenharmony_ci#include <linux/mm.h> 288c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 298c2ecf20Sopenharmony_ci#include "pmem.h" 308c2ecf20Sopenharmony_ci#include "pfn.h" 318c2ecf20Sopenharmony_ci#include "nd.h" 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic struct device *to_dev(struct pmem_device *pmem) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci /* 368c2ecf20Sopenharmony_ci * nvdimm bus services need a 'dev' parameter, and we record the device 378c2ecf20Sopenharmony_ci * at init in bb.dev. 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_ci return pmem->bb.dev; 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic struct nd_region *to_region(struct pmem_device *pmem) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci return to_nd_region(to_dev(pmem)->parent); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic void hwpoison_clear(struct pmem_device *pmem, 488c2ecf20Sopenharmony_ci phys_addr_t phys, unsigned int len) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci unsigned long pfn_start, pfn_end, pfn; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* only pmem in the linear map supports HWPoison */ 538c2ecf20Sopenharmony_ci if (is_vmalloc_addr(pmem->virt_addr)) 548c2ecf20Sopenharmony_ci return; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci pfn_start = PHYS_PFN(phys); 578c2ecf20Sopenharmony_ci pfn_end = pfn_start + PHYS_PFN(len); 588c2ecf20Sopenharmony_ci for (pfn = pfn_start; pfn < pfn_end; pfn++) { 598c2ecf20Sopenharmony_ci struct page *page = pfn_to_page(pfn); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci /* 628c2ecf20Sopenharmony_ci * Note, no need to hold a get_dev_pagemap() reference 638c2ecf20Sopenharmony_ci * here since we're in the driver I/O path and 648c2ecf20Sopenharmony_ci * outstanding I/O requests pin the dev_pagemap. 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_ci if (test_and_clear_pmem_poison(page)) 678c2ecf20Sopenharmony_ci clear_mce_nospec(pfn); 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic blk_status_t pmem_clear_poison(struct pmem_device *pmem, 728c2ecf20Sopenharmony_ci phys_addr_t offset, unsigned int len) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci struct device *dev = to_dev(pmem); 758c2ecf20Sopenharmony_ci sector_t sector; 768c2ecf20Sopenharmony_ci long cleared; 778c2ecf20Sopenharmony_ci blk_status_t rc = BLK_STS_OK; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci sector = (offset - pmem->data_offset) / 512; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci cleared = nvdimm_clear_poison(dev, pmem->phys_addr + offset, len); 828c2ecf20Sopenharmony_ci if (cleared < len) 838c2ecf20Sopenharmony_ci rc = BLK_STS_IOERR; 848c2ecf20Sopenharmony_ci if (cleared > 0 && cleared / 512) { 858c2ecf20Sopenharmony_ci hwpoison_clear(pmem, pmem->phys_addr + offset, cleared); 868c2ecf20Sopenharmony_ci cleared /= 512; 878c2ecf20Sopenharmony_ci dev_dbg(dev, "%#llx clear %ld sector%s\n", 888c2ecf20Sopenharmony_ci (unsigned long long) sector, cleared, 898c2ecf20Sopenharmony_ci cleared > 1 ? "s" : ""); 908c2ecf20Sopenharmony_ci badblocks_clear(&pmem->bb, sector, cleared); 918c2ecf20Sopenharmony_ci if (pmem->bb_state) 928c2ecf20Sopenharmony_ci sysfs_notify_dirent(pmem->bb_state); 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci arch_invalidate_pmem(pmem->virt_addr + offset, len); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return rc; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void write_pmem(void *pmem_addr, struct page *page, 1018c2ecf20Sopenharmony_ci unsigned int off, unsigned int len) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci unsigned int chunk; 1048c2ecf20Sopenharmony_ci void *mem; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci while (len) { 1078c2ecf20Sopenharmony_ci mem = kmap_atomic(page); 1088c2ecf20Sopenharmony_ci chunk = min_t(unsigned int, len, PAGE_SIZE - off); 1098c2ecf20Sopenharmony_ci memcpy_flushcache(pmem_addr, mem + off, chunk); 1108c2ecf20Sopenharmony_ci kunmap_atomic(mem); 1118c2ecf20Sopenharmony_ci len -= chunk; 1128c2ecf20Sopenharmony_ci off = 0; 1138c2ecf20Sopenharmony_ci page++; 1148c2ecf20Sopenharmony_ci pmem_addr += chunk; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic blk_status_t read_pmem(struct page *page, unsigned int off, 1198c2ecf20Sopenharmony_ci void *pmem_addr, unsigned int len) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci unsigned int chunk; 1228c2ecf20Sopenharmony_ci unsigned long rem; 1238c2ecf20Sopenharmony_ci void *mem; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci while (len) { 1268c2ecf20Sopenharmony_ci mem = kmap_atomic(page); 1278c2ecf20Sopenharmony_ci chunk = min_t(unsigned int, len, PAGE_SIZE - off); 1288c2ecf20Sopenharmony_ci rem = copy_mc_to_kernel(mem + off, pmem_addr, chunk); 1298c2ecf20Sopenharmony_ci kunmap_atomic(mem); 1308c2ecf20Sopenharmony_ci if (rem) 1318c2ecf20Sopenharmony_ci return BLK_STS_IOERR; 1328c2ecf20Sopenharmony_ci len -= chunk; 1338c2ecf20Sopenharmony_ci off = 0; 1348c2ecf20Sopenharmony_ci page++; 1358c2ecf20Sopenharmony_ci pmem_addr += chunk; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci return BLK_STS_OK; 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic blk_status_t pmem_do_read(struct pmem_device *pmem, 1418c2ecf20Sopenharmony_ci struct page *page, unsigned int page_off, 1428c2ecf20Sopenharmony_ci sector_t sector, unsigned int len) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci blk_status_t rc; 1458c2ecf20Sopenharmony_ci phys_addr_t pmem_off = sector * 512 + pmem->data_offset; 1468c2ecf20Sopenharmony_ci void *pmem_addr = pmem->virt_addr + pmem_off; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (unlikely(is_bad_pmem(&pmem->bb, sector, len))) 1498c2ecf20Sopenharmony_ci return BLK_STS_IOERR; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci rc = read_pmem(page, page_off, pmem_addr, len); 1528c2ecf20Sopenharmony_ci flush_dcache_page(page); 1538c2ecf20Sopenharmony_ci return rc; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic blk_status_t pmem_do_write(struct pmem_device *pmem, 1578c2ecf20Sopenharmony_ci struct page *page, unsigned int page_off, 1588c2ecf20Sopenharmony_ci sector_t sector, unsigned int len) 1598c2ecf20Sopenharmony_ci{ 1608c2ecf20Sopenharmony_ci blk_status_t rc = BLK_STS_OK; 1618c2ecf20Sopenharmony_ci bool bad_pmem = false; 1628c2ecf20Sopenharmony_ci phys_addr_t pmem_off = sector * 512 + pmem->data_offset; 1638c2ecf20Sopenharmony_ci void *pmem_addr = pmem->virt_addr + pmem_off; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (unlikely(is_bad_pmem(&pmem->bb, sector, len))) 1668c2ecf20Sopenharmony_ci bad_pmem = true; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* 1698c2ecf20Sopenharmony_ci * Note that we write the data both before and after 1708c2ecf20Sopenharmony_ci * clearing poison. The write before clear poison 1718c2ecf20Sopenharmony_ci * handles situations where the latest written data is 1728c2ecf20Sopenharmony_ci * preserved and the clear poison operation simply marks 1738c2ecf20Sopenharmony_ci * the address range as valid without changing the data. 1748c2ecf20Sopenharmony_ci * In this case application software can assume that an 1758c2ecf20Sopenharmony_ci * interrupted write will either return the new good 1768c2ecf20Sopenharmony_ci * data or an error. 1778c2ecf20Sopenharmony_ci * 1788c2ecf20Sopenharmony_ci * However, if pmem_clear_poison() leaves the data in an 1798c2ecf20Sopenharmony_ci * indeterminate state we need to perform the write 1808c2ecf20Sopenharmony_ci * after clear poison. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci flush_dcache_page(page); 1838c2ecf20Sopenharmony_ci write_pmem(pmem_addr, page, page_off, len); 1848c2ecf20Sopenharmony_ci if (unlikely(bad_pmem)) { 1858c2ecf20Sopenharmony_ci rc = pmem_clear_poison(pmem, pmem_off, len); 1868c2ecf20Sopenharmony_ci write_pmem(pmem_addr, page, page_off, len); 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return rc; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic blk_qc_t pmem_submit_bio(struct bio *bio) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci int ret = 0; 1958c2ecf20Sopenharmony_ci blk_status_t rc = 0; 1968c2ecf20Sopenharmony_ci bool do_acct; 1978c2ecf20Sopenharmony_ci unsigned long start; 1988c2ecf20Sopenharmony_ci struct bio_vec bvec; 1998c2ecf20Sopenharmony_ci struct bvec_iter iter; 2008c2ecf20Sopenharmony_ci struct pmem_device *pmem = bio->bi_disk->private_data; 2018c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_region(pmem); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (bio->bi_opf & REQ_PREFLUSH) 2048c2ecf20Sopenharmony_ci ret = nvdimm_flush(nd_region, bio); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci do_acct = blk_queue_io_stat(bio->bi_disk->queue); 2078c2ecf20Sopenharmony_ci if (do_acct) 2088c2ecf20Sopenharmony_ci start = bio_start_io_acct(bio); 2098c2ecf20Sopenharmony_ci bio_for_each_segment(bvec, bio, iter) { 2108c2ecf20Sopenharmony_ci if (op_is_write(bio_op(bio))) 2118c2ecf20Sopenharmony_ci rc = pmem_do_write(pmem, bvec.bv_page, bvec.bv_offset, 2128c2ecf20Sopenharmony_ci iter.bi_sector, bvec.bv_len); 2138c2ecf20Sopenharmony_ci else 2148c2ecf20Sopenharmony_ci rc = pmem_do_read(pmem, bvec.bv_page, bvec.bv_offset, 2158c2ecf20Sopenharmony_ci iter.bi_sector, bvec.bv_len); 2168c2ecf20Sopenharmony_ci if (rc) { 2178c2ecf20Sopenharmony_ci bio->bi_status = rc; 2188c2ecf20Sopenharmony_ci break; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci if (do_acct) 2228c2ecf20Sopenharmony_ci bio_end_io_acct(bio, start); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci if (bio->bi_opf & REQ_FUA) 2258c2ecf20Sopenharmony_ci ret = nvdimm_flush(nd_region, bio); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci if (ret) 2288c2ecf20Sopenharmony_ci bio->bi_status = errno_to_blk_status(ret); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci bio_endio(bio); 2318c2ecf20Sopenharmony_ci return BLK_QC_T_NONE; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic int pmem_rw_page(struct block_device *bdev, sector_t sector, 2358c2ecf20Sopenharmony_ci struct page *page, unsigned int op) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci struct pmem_device *pmem = bdev->bd_disk->private_data; 2388c2ecf20Sopenharmony_ci blk_status_t rc; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci if (op_is_write(op)) 2418c2ecf20Sopenharmony_ci rc = pmem_do_write(pmem, page, 0, sector, thp_size(page)); 2428c2ecf20Sopenharmony_ci else 2438c2ecf20Sopenharmony_ci rc = pmem_do_read(pmem, page, 0, sector, thp_size(page)); 2448c2ecf20Sopenharmony_ci /* 2458c2ecf20Sopenharmony_ci * The ->rw_page interface is subtle and tricky. The core 2468c2ecf20Sopenharmony_ci * retries on any error, so we can only invoke page_endio() in 2478c2ecf20Sopenharmony_ci * the successful completion case. Otherwise, we'll see crashes 2488c2ecf20Sopenharmony_ci * caused by double completion. 2498c2ecf20Sopenharmony_ci */ 2508c2ecf20Sopenharmony_ci if (rc == 0) 2518c2ecf20Sopenharmony_ci page_endio(page, op_is_write(op), 0); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci return blk_status_to_errno(rc); 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci/* see "strong" declaration in tools/testing/nvdimm/pmem-dax.c */ 2578c2ecf20Sopenharmony_ci__weak long __pmem_direct_access(struct pmem_device *pmem, pgoff_t pgoff, 2588c2ecf20Sopenharmony_ci long nr_pages, void **kaddr, pfn_t *pfn) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci resource_size_t offset = PFN_PHYS(pgoff) + pmem->data_offset; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci if (unlikely(is_bad_pmem(&pmem->bb, PFN_PHYS(pgoff) / 512, 2638c2ecf20Sopenharmony_ci PFN_PHYS(nr_pages)))) 2648c2ecf20Sopenharmony_ci return -EIO; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci if (kaddr) 2678c2ecf20Sopenharmony_ci *kaddr = pmem->virt_addr + offset; 2688c2ecf20Sopenharmony_ci if (pfn) 2698c2ecf20Sopenharmony_ci *pfn = phys_to_pfn_t(pmem->phys_addr + offset, pmem->pfn_flags); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* 2728c2ecf20Sopenharmony_ci * If badblocks are present, limit known good range to the 2738c2ecf20Sopenharmony_ci * requested range. 2748c2ecf20Sopenharmony_ci */ 2758c2ecf20Sopenharmony_ci if (unlikely(pmem->bb.count)) 2768c2ecf20Sopenharmony_ci return nr_pages; 2778c2ecf20Sopenharmony_ci return PHYS_PFN(pmem->size - pmem->pfn_pad - offset); 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic const struct block_device_operations pmem_fops = { 2818c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2828c2ecf20Sopenharmony_ci .submit_bio = pmem_submit_bio, 2838c2ecf20Sopenharmony_ci .rw_page = pmem_rw_page, 2848c2ecf20Sopenharmony_ci}; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic int pmem_dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff, 2878c2ecf20Sopenharmony_ci size_t nr_pages) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct pmem_device *pmem = dax_get_private(dax_dev); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci return blk_status_to_errno(pmem_do_write(pmem, ZERO_PAGE(0), 0, 2928c2ecf20Sopenharmony_ci PFN_PHYS(pgoff) >> SECTOR_SHIFT, 2938c2ecf20Sopenharmony_ci PAGE_SIZE)); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic long pmem_dax_direct_access(struct dax_device *dax_dev, 2978c2ecf20Sopenharmony_ci pgoff_t pgoff, long nr_pages, void **kaddr, pfn_t *pfn) 2988c2ecf20Sopenharmony_ci{ 2998c2ecf20Sopenharmony_ci struct pmem_device *pmem = dax_get_private(dax_dev); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci return __pmem_direct_access(pmem, pgoff, nr_pages, kaddr, pfn); 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci/* 3058c2ecf20Sopenharmony_ci * Use the 'no check' versions of copy_from_iter_flushcache() and 3068c2ecf20Sopenharmony_ci * copy_mc_to_iter() to bypass HARDENED_USERCOPY overhead. Bounds 3078c2ecf20Sopenharmony_ci * checking, both file offset and device offset, is handled by 3088c2ecf20Sopenharmony_ci * dax_iomap_actor() 3098c2ecf20Sopenharmony_ci */ 3108c2ecf20Sopenharmony_cistatic size_t pmem_copy_from_iter(struct dax_device *dax_dev, pgoff_t pgoff, 3118c2ecf20Sopenharmony_ci void *addr, size_t bytes, struct iov_iter *i) 3128c2ecf20Sopenharmony_ci{ 3138c2ecf20Sopenharmony_ci return _copy_from_iter_flushcache(addr, bytes, i); 3148c2ecf20Sopenharmony_ci} 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cistatic size_t pmem_copy_to_iter(struct dax_device *dax_dev, pgoff_t pgoff, 3178c2ecf20Sopenharmony_ci void *addr, size_t bytes, struct iov_iter *i) 3188c2ecf20Sopenharmony_ci{ 3198c2ecf20Sopenharmony_ci return _copy_mc_to_iter(addr, bytes, i); 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_cistatic const struct dax_operations pmem_dax_ops = { 3238c2ecf20Sopenharmony_ci .direct_access = pmem_dax_direct_access, 3248c2ecf20Sopenharmony_ci .dax_supported = generic_fsdax_supported, 3258c2ecf20Sopenharmony_ci .copy_from_iter = pmem_copy_from_iter, 3268c2ecf20Sopenharmony_ci .copy_to_iter = pmem_copy_to_iter, 3278c2ecf20Sopenharmony_ci .zero_page_range = pmem_dax_zero_page_range, 3288c2ecf20Sopenharmony_ci}; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_cistatic const struct attribute_group *pmem_attribute_groups[] = { 3318c2ecf20Sopenharmony_ci &dax_attribute_group, 3328c2ecf20Sopenharmony_ci NULL, 3338c2ecf20Sopenharmony_ci}; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_cistatic void pmem_pagemap_cleanup(struct dev_pagemap *pgmap) 3368c2ecf20Sopenharmony_ci{ 3378c2ecf20Sopenharmony_ci struct request_queue *q = 3388c2ecf20Sopenharmony_ci container_of(pgmap->ref, struct request_queue, q_usage_counter); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci blk_cleanup_queue(q); 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_cistatic void pmem_release_queue(void *pgmap) 3448c2ecf20Sopenharmony_ci{ 3458c2ecf20Sopenharmony_ci pmem_pagemap_cleanup(pgmap); 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic void pmem_pagemap_kill(struct dev_pagemap *pgmap) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci struct request_queue *q = 3518c2ecf20Sopenharmony_ci container_of(pgmap->ref, struct request_queue, q_usage_counter); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci blk_freeze_queue_start(q); 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_cistatic void pmem_release_disk(void *__pmem) 3578c2ecf20Sopenharmony_ci{ 3588c2ecf20Sopenharmony_ci struct pmem_device *pmem = __pmem; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci kill_dax(pmem->dax_dev); 3618c2ecf20Sopenharmony_ci put_dax(pmem->dax_dev); 3628c2ecf20Sopenharmony_ci del_gendisk(pmem->disk); 3638c2ecf20Sopenharmony_ci put_disk(pmem->disk); 3648c2ecf20Sopenharmony_ci} 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_cistatic const struct dev_pagemap_ops fsdax_pagemap_ops = { 3678c2ecf20Sopenharmony_ci .kill = pmem_pagemap_kill, 3688c2ecf20Sopenharmony_ci .cleanup = pmem_pagemap_cleanup, 3698c2ecf20Sopenharmony_ci}; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_cistatic int pmem_attach_disk(struct device *dev, 3728c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns) 3738c2ecf20Sopenharmony_ci{ 3748c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); 3758c2ecf20Sopenharmony_ci struct nd_region *nd_region = to_nd_region(dev->parent); 3768c2ecf20Sopenharmony_ci int nid = dev_to_node(dev), fua; 3778c2ecf20Sopenharmony_ci struct resource *res = &nsio->res; 3788c2ecf20Sopenharmony_ci struct range bb_range; 3798c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = NULL; 3808c2ecf20Sopenharmony_ci struct dax_device *dax_dev; 3818c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb; 3828c2ecf20Sopenharmony_ci struct pmem_device *pmem; 3838c2ecf20Sopenharmony_ci struct request_queue *q; 3848c2ecf20Sopenharmony_ci struct device *gendev; 3858c2ecf20Sopenharmony_ci struct gendisk *disk; 3868c2ecf20Sopenharmony_ci void *addr; 3878c2ecf20Sopenharmony_ci int rc; 3888c2ecf20Sopenharmony_ci unsigned long flags = 0UL; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci pmem = devm_kzalloc(dev, sizeof(*pmem), GFP_KERNEL); 3918c2ecf20Sopenharmony_ci if (!pmem) 3928c2ecf20Sopenharmony_ci return -ENOMEM; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci rc = devm_namespace_enable(dev, ndns, nd_info_block_reserve()); 3958c2ecf20Sopenharmony_ci if (rc) 3968c2ecf20Sopenharmony_ci return rc; 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci /* while nsio_rw_bytes is active, parse a pfn info block if present */ 3998c2ecf20Sopenharmony_ci if (is_nd_pfn(dev)) { 4008c2ecf20Sopenharmony_ci nd_pfn = to_nd_pfn(dev); 4018c2ecf20Sopenharmony_ci rc = nvdimm_setup_pfn(nd_pfn, &pmem->pgmap); 4028c2ecf20Sopenharmony_ci if (rc) 4038c2ecf20Sopenharmony_ci return rc; 4048c2ecf20Sopenharmony_ci } 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci /* we're attaching a block device, disable raw namespace access */ 4078c2ecf20Sopenharmony_ci devm_namespace_disable(dev, ndns); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci dev_set_drvdata(dev, pmem); 4108c2ecf20Sopenharmony_ci pmem->phys_addr = res->start; 4118c2ecf20Sopenharmony_ci pmem->size = resource_size(res); 4128c2ecf20Sopenharmony_ci fua = nvdimm_has_flush(nd_region); 4138c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) || fua < 0) { 4148c2ecf20Sopenharmony_ci dev_warn(dev, "unable to guarantee persistence of writes\n"); 4158c2ecf20Sopenharmony_ci fua = 0; 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci if (!devm_request_mem_region(dev, res->start, resource_size(res), 4198c2ecf20Sopenharmony_ci dev_name(&ndns->dev))) { 4208c2ecf20Sopenharmony_ci dev_warn(dev, "could not reserve region %pR\n", res); 4218c2ecf20Sopenharmony_ci return -EBUSY; 4228c2ecf20Sopenharmony_ci } 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci q = blk_alloc_queue(dev_to_node(dev)); 4258c2ecf20Sopenharmony_ci if (!q) 4268c2ecf20Sopenharmony_ci return -ENOMEM; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci pmem->pfn_flags = PFN_DEV; 4298c2ecf20Sopenharmony_ci pmem->pgmap.ref = &q->q_usage_counter; 4308c2ecf20Sopenharmony_ci if (is_nd_pfn(dev)) { 4318c2ecf20Sopenharmony_ci pmem->pgmap.type = MEMORY_DEVICE_FS_DAX; 4328c2ecf20Sopenharmony_ci pmem->pgmap.ops = &fsdax_pagemap_ops; 4338c2ecf20Sopenharmony_ci addr = devm_memremap_pages(dev, &pmem->pgmap); 4348c2ecf20Sopenharmony_ci pfn_sb = nd_pfn->pfn_sb; 4358c2ecf20Sopenharmony_ci pmem->data_offset = le64_to_cpu(pfn_sb->dataoff); 4368c2ecf20Sopenharmony_ci pmem->pfn_pad = resource_size(res) - 4378c2ecf20Sopenharmony_ci range_len(&pmem->pgmap.range); 4388c2ecf20Sopenharmony_ci pmem->pfn_flags |= PFN_MAP; 4398c2ecf20Sopenharmony_ci bb_range = pmem->pgmap.range; 4408c2ecf20Sopenharmony_ci bb_range.start += pmem->data_offset; 4418c2ecf20Sopenharmony_ci } else if (pmem_should_map_pages(dev)) { 4428c2ecf20Sopenharmony_ci pmem->pgmap.range.start = res->start; 4438c2ecf20Sopenharmony_ci pmem->pgmap.range.end = res->end; 4448c2ecf20Sopenharmony_ci pmem->pgmap.nr_range = 1; 4458c2ecf20Sopenharmony_ci pmem->pgmap.type = MEMORY_DEVICE_FS_DAX; 4468c2ecf20Sopenharmony_ci pmem->pgmap.ops = &fsdax_pagemap_ops; 4478c2ecf20Sopenharmony_ci addr = devm_memremap_pages(dev, &pmem->pgmap); 4488c2ecf20Sopenharmony_ci pmem->pfn_flags |= PFN_MAP; 4498c2ecf20Sopenharmony_ci bb_range = pmem->pgmap.range; 4508c2ecf20Sopenharmony_ci } else { 4518c2ecf20Sopenharmony_ci addr = devm_memremap(dev, pmem->phys_addr, 4528c2ecf20Sopenharmony_ci pmem->size, ARCH_MEMREMAP_PMEM); 4538c2ecf20Sopenharmony_ci if (devm_add_action_or_reset(dev, pmem_release_queue, 4548c2ecf20Sopenharmony_ci &pmem->pgmap)) 4558c2ecf20Sopenharmony_ci return -ENOMEM; 4568c2ecf20Sopenharmony_ci bb_range.start = res->start; 4578c2ecf20Sopenharmony_ci bb_range.end = res->end; 4588c2ecf20Sopenharmony_ci } 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci if (IS_ERR(addr)) 4618c2ecf20Sopenharmony_ci return PTR_ERR(addr); 4628c2ecf20Sopenharmony_ci pmem->virt_addr = addr; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci blk_queue_write_cache(q, true, fua); 4658c2ecf20Sopenharmony_ci blk_queue_physical_block_size(q, PAGE_SIZE); 4668c2ecf20Sopenharmony_ci blk_queue_logical_block_size(q, pmem_sector_size(ndns)); 4678c2ecf20Sopenharmony_ci blk_queue_max_hw_sectors(q, UINT_MAX); 4688c2ecf20Sopenharmony_ci blk_queue_flag_set(QUEUE_FLAG_NONROT, q); 4698c2ecf20Sopenharmony_ci if (pmem->pfn_flags & PFN_MAP) 4708c2ecf20Sopenharmony_ci blk_queue_flag_set(QUEUE_FLAG_DAX, q); 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci disk = alloc_disk_node(0, nid); 4738c2ecf20Sopenharmony_ci if (!disk) 4748c2ecf20Sopenharmony_ci return -ENOMEM; 4758c2ecf20Sopenharmony_ci pmem->disk = disk; 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci disk->fops = &pmem_fops; 4788c2ecf20Sopenharmony_ci disk->queue = q; 4798c2ecf20Sopenharmony_ci disk->flags = GENHD_FL_EXT_DEVT; 4808c2ecf20Sopenharmony_ci disk->private_data = pmem; 4818c2ecf20Sopenharmony_ci nvdimm_namespace_disk_name(ndns, disk->disk_name); 4828c2ecf20Sopenharmony_ci set_capacity(disk, (pmem->size - pmem->pfn_pad - pmem->data_offset) 4838c2ecf20Sopenharmony_ci / 512); 4848c2ecf20Sopenharmony_ci if (devm_init_badblocks(dev, &pmem->bb)) 4858c2ecf20Sopenharmony_ci return -ENOMEM; 4868c2ecf20Sopenharmony_ci nvdimm_badblocks_populate(nd_region, &pmem->bb, &bb_range); 4878c2ecf20Sopenharmony_ci disk->bb = &pmem->bb; 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci if (is_nvdimm_sync(nd_region)) 4908c2ecf20Sopenharmony_ci flags = DAXDEV_F_SYNC; 4918c2ecf20Sopenharmony_ci dax_dev = alloc_dax(pmem, disk->disk_name, &pmem_dax_ops, flags); 4928c2ecf20Sopenharmony_ci if (IS_ERR(dax_dev)) { 4938c2ecf20Sopenharmony_ci put_disk(disk); 4948c2ecf20Sopenharmony_ci return PTR_ERR(dax_dev); 4958c2ecf20Sopenharmony_ci } 4968c2ecf20Sopenharmony_ci dax_write_cache(dax_dev, nvdimm_has_cache(nd_region)); 4978c2ecf20Sopenharmony_ci pmem->dax_dev = dax_dev; 4988c2ecf20Sopenharmony_ci gendev = disk_to_dev(disk); 4998c2ecf20Sopenharmony_ci gendev->groups = pmem_attribute_groups; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci device_add_disk(dev, disk, NULL); 5028c2ecf20Sopenharmony_ci if (devm_add_action_or_reset(dev, pmem_release_disk, pmem)) 5038c2ecf20Sopenharmony_ci return -ENOMEM; 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci nvdimm_check_and_set_ro(disk); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci pmem->bb_state = sysfs_get_dirent(disk_to_dev(disk)->kobj.sd, 5088c2ecf20Sopenharmony_ci "badblocks"); 5098c2ecf20Sopenharmony_ci if (!pmem->bb_state) 5108c2ecf20Sopenharmony_ci dev_warn(dev, "'badblocks' notification disabled\n"); 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci return 0; 5138c2ecf20Sopenharmony_ci} 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_cistatic int nd_pmem_probe(struct device *dev) 5168c2ecf20Sopenharmony_ci{ 5178c2ecf20Sopenharmony_ci int ret; 5188c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns; 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci ndns = nvdimm_namespace_common_probe(dev); 5218c2ecf20Sopenharmony_ci if (IS_ERR(ndns)) 5228c2ecf20Sopenharmony_ci return PTR_ERR(ndns); 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci if (is_nd_btt(dev)) 5258c2ecf20Sopenharmony_ci return nvdimm_namespace_attach_btt(ndns); 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci if (is_nd_pfn(dev)) 5288c2ecf20Sopenharmony_ci return pmem_attach_disk(dev, ndns); 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci ret = devm_namespace_enable(dev, ndns, nd_info_block_reserve()); 5318c2ecf20Sopenharmony_ci if (ret) 5328c2ecf20Sopenharmony_ci return ret; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci ret = nd_btt_probe(dev, ndns); 5358c2ecf20Sopenharmony_ci if (ret == 0) 5368c2ecf20Sopenharmony_ci return -ENXIO; 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci /* 5398c2ecf20Sopenharmony_ci * We have two failure conditions here, there is no 5408c2ecf20Sopenharmony_ci * info reserver block or we found a valid info reserve block 5418c2ecf20Sopenharmony_ci * but failed to initialize the pfn superblock. 5428c2ecf20Sopenharmony_ci * 5438c2ecf20Sopenharmony_ci * For the first case consider namespace as a raw pmem namespace 5448c2ecf20Sopenharmony_ci * and attach a disk. 5458c2ecf20Sopenharmony_ci * 5468c2ecf20Sopenharmony_ci * For the latter, consider this a success and advance the namespace 5478c2ecf20Sopenharmony_ci * seed. 5488c2ecf20Sopenharmony_ci */ 5498c2ecf20Sopenharmony_ci ret = nd_pfn_probe(dev, ndns); 5508c2ecf20Sopenharmony_ci if (ret == 0) 5518c2ecf20Sopenharmony_ci return -ENXIO; 5528c2ecf20Sopenharmony_ci else if (ret == -EOPNOTSUPP) 5538c2ecf20Sopenharmony_ci return ret; 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci ret = nd_dax_probe(dev, ndns); 5568c2ecf20Sopenharmony_ci if (ret == 0) 5578c2ecf20Sopenharmony_ci return -ENXIO; 5588c2ecf20Sopenharmony_ci else if (ret == -EOPNOTSUPP) 5598c2ecf20Sopenharmony_ci return ret; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci /* probe complete, attach handles namespace enabling */ 5628c2ecf20Sopenharmony_ci devm_namespace_disable(dev, ndns); 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci return pmem_attach_disk(dev, ndns); 5658c2ecf20Sopenharmony_ci} 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_cistatic int nd_pmem_remove(struct device *dev) 5688c2ecf20Sopenharmony_ci{ 5698c2ecf20Sopenharmony_ci struct pmem_device *pmem = dev_get_drvdata(dev); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci if (is_nd_btt(dev)) 5728c2ecf20Sopenharmony_ci nvdimm_namespace_detach_btt(to_nd_btt(dev)); 5738c2ecf20Sopenharmony_ci else { 5748c2ecf20Sopenharmony_ci /* 5758c2ecf20Sopenharmony_ci * Note, this assumes nd_device_lock() context to not 5768c2ecf20Sopenharmony_ci * race nd_pmem_notify() 5778c2ecf20Sopenharmony_ci */ 5788c2ecf20Sopenharmony_ci sysfs_put(pmem->bb_state); 5798c2ecf20Sopenharmony_ci pmem->bb_state = NULL; 5808c2ecf20Sopenharmony_ci } 5818c2ecf20Sopenharmony_ci nvdimm_flush(to_nd_region(dev->parent), NULL); 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci return 0; 5848c2ecf20Sopenharmony_ci} 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_cistatic void nd_pmem_shutdown(struct device *dev) 5878c2ecf20Sopenharmony_ci{ 5888c2ecf20Sopenharmony_ci nvdimm_flush(to_nd_region(dev->parent), NULL); 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_cistatic void nd_pmem_notify(struct device *dev, enum nvdimm_event event) 5928c2ecf20Sopenharmony_ci{ 5938c2ecf20Sopenharmony_ci struct nd_region *nd_region; 5948c2ecf20Sopenharmony_ci resource_size_t offset = 0, end_trunc = 0; 5958c2ecf20Sopenharmony_ci struct nd_namespace_common *ndns; 5968c2ecf20Sopenharmony_ci struct nd_namespace_io *nsio; 5978c2ecf20Sopenharmony_ci struct badblocks *bb; 5988c2ecf20Sopenharmony_ci struct range range; 5998c2ecf20Sopenharmony_ci struct kernfs_node *bb_state; 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci if (event != NVDIMM_REVALIDATE_POISON) 6028c2ecf20Sopenharmony_ci return; 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_ci if (is_nd_btt(dev)) { 6058c2ecf20Sopenharmony_ci struct nd_btt *nd_btt = to_nd_btt(dev); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci ndns = nd_btt->ndns; 6088c2ecf20Sopenharmony_ci nd_region = to_nd_region(ndns->dev.parent); 6098c2ecf20Sopenharmony_ci nsio = to_nd_namespace_io(&ndns->dev); 6108c2ecf20Sopenharmony_ci bb = &nsio->bb; 6118c2ecf20Sopenharmony_ci bb_state = NULL; 6128c2ecf20Sopenharmony_ci } else { 6138c2ecf20Sopenharmony_ci struct pmem_device *pmem = dev_get_drvdata(dev); 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci nd_region = to_region(pmem); 6168c2ecf20Sopenharmony_ci bb = &pmem->bb; 6178c2ecf20Sopenharmony_ci bb_state = pmem->bb_state; 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_ci if (is_nd_pfn(dev)) { 6208c2ecf20Sopenharmony_ci struct nd_pfn *nd_pfn = to_nd_pfn(dev); 6218c2ecf20Sopenharmony_ci struct nd_pfn_sb *pfn_sb = nd_pfn->pfn_sb; 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci ndns = nd_pfn->ndns; 6248c2ecf20Sopenharmony_ci offset = pmem->data_offset + 6258c2ecf20Sopenharmony_ci __le32_to_cpu(pfn_sb->start_pad); 6268c2ecf20Sopenharmony_ci end_trunc = __le32_to_cpu(pfn_sb->end_trunc); 6278c2ecf20Sopenharmony_ci } else { 6288c2ecf20Sopenharmony_ci ndns = to_ndns(dev); 6298c2ecf20Sopenharmony_ci } 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_ci nsio = to_nd_namespace_io(&ndns->dev); 6328c2ecf20Sopenharmony_ci } 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci range.start = nsio->res.start + offset; 6358c2ecf20Sopenharmony_ci range.end = nsio->res.end - end_trunc; 6368c2ecf20Sopenharmony_ci nvdimm_badblocks_populate(nd_region, bb, &range); 6378c2ecf20Sopenharmony_ci if (bb_state) 6388c2ecf20Sopenharmony_ci sysfs_notify_dirent(bb_state); 6398c2ecf20Sopenharmony_ci} 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ciMODULE_ALIAS("pmem"); 6428c2ecf20Sopenharmony_ciMODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_IO); 6438c2ecf20Sopenharmony_ciMODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_PMEM); 6448c2ecf20Sopenharmony_cistatic struct nd_device_driver nd_pmem_driver = { 6458c2ecf20Sopenharmony_ci .probe = nd_pmem_probe, 6468c2ecf20Sopenharmony_ci .remove = nd_pmem_remove, 6478c2ecf20Sopenharmony_ci .notify = nd_pmem_notify, 6488c2ecf20Sopenharmony_ci .shutdown = nd_pmem_shutdown, 6498c2ecf20Sopenharmony_ci .drv = { 6508c2ecf20Sopenharmony_ci .name = "nd_pmem", 6518c2ecf20Sopenharmony_ci }, 6528c2ecf20Sopenharmony_ci .type = ND_DRIVER_NAMESPACE_IO | ND_DRIVER_NAMESPACE_PMEM, 6538c2ecf20Sopenharmony_ci}; 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_cimodule_nd_driver(nd_pmem_driver); 6568c2ecf20Sopenharmony_ci 6578c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ross Zwisler <ross.zwisler@linux.intel.com>"); 6588c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 659