Pull libnvdimm fixes from Dan Williams: "A regression fix for the Address Range Scrub implementation, yes another one, and support for platforms that misalign persistent memory relative to the Linux memory hotplug section constraint. Longer term, support for sub-section memory hotplug would alleviate alignment waste, but until then this hack allows a 'struct page' memmap to be established for these misaligned memory regions. These have all appeared in a -next release, and thanks to Patrick for reporting and testing the alignment padding fix. Summary: - Unless and until the core mm handles memory hotplug units smaller than a section (128M), persistent memory namespaces must be padded to section alignment. The libnvdimm core already handled section collision with "System RAM", but some configurations overlap independent "Persistent Memory" ranges within a section, so additional padding injection is added for that case. - The recent reworks of the ARS (address range scrub) state machine to reduce the number of state flags inadvertantly missed a conversion of acpi_nfit_ars_rescan() call sites. Fix the regression whereby user-requested ARS results in a "short" scrub rather than a "long" scrub. - Fixup the unit tests to handle / test the 128M section alignment of mocked test resources. * tag 'libnvdimm-fixes-4.20-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm: acpi/nfit: Fix user-initiated ARS to be "ARS-long" rather than "ARS-short" libnvdimm, pfn: Pad pfn namespaces relative to other regions tools/testing/nvdimm: Align test resources to 128M
Unified · Split
| @@ -1308,7 +1308,7 @@ static ssize_t scrub_store(struct device *dev, · 1307 unmodified lines | ||
| 1308 | 1308 | if (nd_desc) { |
| 1309 | 1309 | struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc); |
| 1310 | 1310 | |
| 1311 | − rc = acpi_nfit_ars_rescan(acpi_desc, 0); | |
| 1311 | + rc = acpi_nfit_ars_rescan(acpi_desc, ARS_REQ_LONG); | |
| 1312 | 1312 | } |
| 1313 | 1313 | device_unlock(dev); |
| 1314 | 1314 | if (rc) |
| @@ -111,6 +111,8 @@ resource_size_t nd_pmem_available_dpa(struct nd_region *nd_region, · 110 unmodified lines | ||
| 111 | 111 | struct nd_mapping *nd_mapping, resource_size_t *overlap); |
| 112 | 112 | resource_size_t nd_blk_available_dpa(struct nd_region *nd_region); |
| 113 | 113 | resource_size_t nd_region_available_dpa(struct nd_region *nd_region); |
| 114 | +int nd_region_conflict(struct nd_region *nd_region, resource_size_t start, | |
| 115 | + resource_size_t size); | |
| 114 | 116 | resource_size_t nvdimm_allocated_dpa(struct nvdimm_drvdata *ndd, |
| 115 | 117 | struct nd_label_id *label_id); |
| 116 | 118 | int alias_dpa_busy(struct device *dev, void *data); |
| @@ -649,14 +649,47 @@ static u64 phys_pmem_align_down(struct nd_pfn *nd_pfn, u64 phys) · 648 unmodified lines | ||
| 649 | 649 | ALIGN_DOWN(phys, nd_pfn->align)); |
| 650 | 650 | } |
| 651 | 651 | |
| 652 | +/* | |
| 653 | + * Check if pmem collides with 'System RAM', or other regions when | |
| 654 | + * section aligned. Trim it accordingly. | |
| 655 | + */ | |
| 656 | +static void trim_pfn_device(struct nd_pfn *nd_pfn, u32 *start_pad, u32 *end_trunc) | |
| 657 | +{ | |
| 658 | + struct nd_namespace_common *ndns = nd_pfn->ndns; | |
| 659 | + struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); | |
| 660 | + struct nd_region *nd_region = to_nd_region(nd_pfn->dev.parent); | |
| 661 | + const resource_size_t start = nsio->res.start; | |
| 662 | + const resource_size_t end = start + resource_size(&nsio->res); | |
| 663 | + resource_size_t adjust, size; | |
| 664 | + | |
| 665 | + *start_pad = 0; | |
| 666 | + *end_trunc = 0; | |
| 667 | + | |
| 668 | + adjust = start - PHYS_SECTION_ALIGN_DOWN(start); | |
| 669 | + size = resource_size(&nsio->res) + adjust; | |
| 670 | + if (region_intersects(start - adjust, size, IORESOURCE_SYSTEM_RAM, | |
| 671 | + IORES_DESC_NONE) == REGION_MIXED | |
| 672 | + || nd_region_conflict(nd_region, start - adjust, size)) | |
| 673 | + *start_pad = PHYS_SECTION_ALIGN_UP(start) - start; | |
| 674 | + | |
| 675 | + /* Now check that end of the range does not collide. */ | |
| 676 | + adjust = PHYS_SECTION_ALIGN_UP(end) - end; | |
| 677 | + size = resource_size(&nsio->res) + adjust; | |
| 678 | + if (region_intersects(start, size, IORESOURCE_SYSTEM_RAM, | |
| 679 | + IORES_DESC_NONE) == REGION_MIXED | |
| 680 | + || !IS_ALIGNED(end, nd_pfn->align) | |
| 681 | + || nd_region_conflict(nd_region, start, size + adjust)) | |
| 682 | + *end_trunc = end - phys_pmem_align_down(nd_pfn, end); | |
| 683 | +} | |
| 684 | + | |
| 652 | 685 | static int nd_pfn_init(struct nd_pfn *nd_pfn) |
| 653 | 686 | { |
| 654 | 687 | u32 dax_label_reserve = is_nd_dax(&nd_pfn->dev) ? SZ_128K : 0; |
| 655 | 688 | struct nd_namespace_common *ndns = nd_pfn->ndns; |
| 656 | − u32 start_pad = 0, end_trunc = 0; | |
| 689 | + struct nd_namespace_io *nsio = to_nd_namespace_io(&ndns->dev); | |
| 657 | 690 | resource_size_t start, size; |
| 658 | − struct nd_namespace_io *nsio; | |
| 659 | 691 | struct nd_region *nd_region; |
| 692 | + u32 start_pad, end_trunc; | |
| 660 | 693 | struct nd_pfn_sb *pfn_sb; |
| 661 | 694 | unsigned long npfns; |
| 662 | 695 | phys_addr_t offset; |
| @@ -688,30 +721,7 @@ static int nd_pfn_init(struct nd_pfn *nd_pfn) · 25 unmodified lines | ||
| 688 | 721 | |
| 689 | 722 | memset(pfn_sb, 0, sizeof(*pfn_sb)); |
| 690 | 723 | |
| 691 | − /* | |
| 692 | − * Check if pmem collides with 'System RAM' when section aligned and | |
| 693 | − * trim it accordingly | |
| 694 | − */ | |
| 695 | − nsio = to_nd_namespace_io(&ndns->dev); | |
| 696 | − start = PHYS_SECTION_ALIGN_DOWN(nsio->res.start); | |
| 697 | − size = resource_size(&nsio->res); | |
| 698 | − if (region_intersects(start, size, IORESOURCE_SYSTEM_RAM, | |
| 699 | − IORES_DESC_NONE) == REGION_MIXED) { | |
| 700 | − start = nsio->res.start; | |
| 701 | − start_pad = PHYS_SECTION_ALIGN_UP(start) - start; | |
| 702 | − } | |
| 703 | − | |
| 704 | − start = nsio->res.start; | |
| 705 | − size = PHYS_SECTION_ALIGN_UP(start + size) - start; | |
| 706 | − if (region_intersects(start, size, IORESOURCE_SYSTEM_RAM, | |
| 707 | − IORES_DESC_NONE) == REGION_MIXED | |
| 708 | − || !IS_ALIGNED(start + resource_size(&nsio->res), | |
| 709 | − nd_pfn->align)) { | |
| 710 | − size = resource_size(&nsio->res); | |
| 711 | − end_trunc = start + size - phys_pmem_align_down(nd_pfn, | |
| 712 | − start + size); | |
| 713 | − } | |
| 714 | − | |
| 724 | + trim_pfn_device(nd_pfn, &start_pad, &end_trunc); | |
| 715 | 725 | if (start_pad + end_trunc) |
| 716 | 726 | dev_info(&nd_pfn->dev, "%s alignment collision, truncate %d bytes\n", |
| 717 | 727 | dev_name(&ndns->dev), start_pad + end_trunc); |
| @@ -722,7 +732,7 @@ static int nd_pfn_init(struct nd_pfn *nd_pfn) · 4 unmodified lines | ||
| 722 | 732 | * implementation will limit the pfns advertised through |
| 723 | 733 | * ->direct_access() to those that are included in the memmap. |
| 724 | 734 | */ |
| 725 | − start += start_pad; | |
| 735 | + start = nsio->res.start + start_pad; | |
| 726 | 736 | size = resource_size(&nsio->res); |
| 727 | 737 | npfns = PFN_SECTION_ALIGN_UP((size - start_pad - end_trunc - SZ_8K) |
| 728 | 738 | / PAGE_SIZE); |
| @@ -1184,6 +1184,47 @@ int nvdimm_has_cache(struct nd_region *nd_region) · 1183 unmodified lines | ||
| 1184 | 1184 | } |
| 1185 | 1185 | EXPORT_SYMBOL_GPL(nvdimm_has_cache); |
| 1186 | 1186 | |
| 1187 | +struct conflict_context { | |
| 1188 | + struct nd_region *nd_region; | |
| 1189 | + resource_size_t start, size; | |
| 1190 | +}; | |
| 1191 | + | |
| 1192 | +static int region_conflict(struct device *dev, void *data) | |
| 1193 | +{ | |
| 1194 | + struct nd_region *nd_region; | |
| 1195 | + struct conflict_context *ctx = data; | |
| 1196 | + resource_size_t res_end, region_end, region_start; | |
| 1197 | + | |
| 1198 | + if (!is_memory(dev)) | |
| 1199 | + return 0; | |
| 1200 | + | |
| 1201 | + nd_region = to_nd_region(dev); | |
| 1202 | + if (nd_region == ctx->nd_region) | |
| 1203 | + return 0; | |
| 1204 | + | |
| 1205 | + res_end = ctx->start + ctx->size; | |
| 1206 | + region_start = nd_region->ndr_start; | |
| 1207 | + region_end = region_start + nd_region->ndr_size; | |
| 1208 | + if (ctx->start >= region_start && ctx->start < region_end) | |
| 1209 | + return -EBUSY; | |
| 1210 | + if (res_end > region_start && res_end <= region_end) | |
| 1211 | + return -EBUSY; | |
| 1212 | + return 0; | |
| 1213 | +} | |
| 1214 | + | |
| 1215 | +int nd_region_conflict(struct nd_region *nd_region, resource_size_t start, | |
| 1216 | + resource_size_t size) | |
| 1217 | +{ | |
| 1218 | + struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&nd_region->dev); | |
| 1219 | + struct conflict_context ctx = { | |
| 1220 | + .nd_region = nd_region, | |
| 1221 | + .start = start, | |
| 1222 | + .size = size, | |
| 1223 | + }; | |
| 1224 | + | |
| 1225 | + return device_for_each_child(&nvdimm_bus->dev, &ctx, region_conflict); | |
| 1226 | +} | |
| 1227 | + | |
| 1187 | 1228 | void __exit nd_region_devs_exit(void) |
| 1188 | 1229 | { |
| 1189 | 1230 | ida_destroy(®ion_ida); |
| @@ -15,6 +15,7 @@ · 14 unmodified lines | ||
| 15 | 15 | #include <linux/dma-mapping.h> |
| 16 | 16 | #include <linux/workqueue.h> |
| 17 | 17 | #include <linux/libnvdimm.h> |
| 18 | +#include <linux/genalloc.h> | |
| 18 | 19 | #include <linux/vmalloc.h> |
| 19 | 20 | #include <linux/device.h> |
| 20 | 21 | #include <linux/module.h> |
| @@ -215,6 +216,8 @@ struct nfit_test { · 194 unmodified lines | ||
| 215 | 216 | |
| 216 | 217 | static struct workqueue_struct *nfit_wq; |
| 217 | 218 | |
| 219 | +static struct gen_pool *nfit_pool; | |
| 220 | + | |
| 218 | 221 | static struct nfit_test *to_nfit_test(struct device *dev) |
| 219 | 222 | { |
| 220 | 223 | struct platform_device *pdev = to_platform_device(dev); |
| @@ -1132,6 +1135,9 @@ static void release_nfit_res(void *data) · 911 unmodified lines | ||
| 1132 | 1135 | list_del(&nfit_res->list); |
| 1133 | 1136 | spin_unlock(&nfit_test_lock); |
| 1134 | 1137 | |
| 1138 | + if (resource_size(&nfit_res->res) >= DIMM_SIZE) | |
| 1139 | + gen_pool_free(nfit_pool, nfit_res->res.start, | |
| 1140 | + resource_size(&nfit_res->res)); | |
| 1135 | 1141 | vfree(nfit_res->buf); |
| 1136 | 1142 | kfree(nfit_res); |
| 1137 | 1143 | } |
| @@ -1144,7 +1150,7 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, · 6 unmodified lines | ||
| 1144 | 1150 | GFP_KERNEL); |
| 1145 | 1151 | int rc; |
| 1146 | 1152 | |
| 1147 | − if (!buf || !nfit_res) | |
| 1153 | + if (!buf || !nfit_res || !*dma) | |
| 1148 | 1154 | goto err; |
| 1149 | 1155 | rc = devm_add_action(dev, release_nfit_res, nfit_res); |
| 1150 | 1156 | if (rc) |
| @@ -1164,6 +1170,8 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, · 13 unmodified lines | ||
| 1164 | 1170 | |
| 1165 | 1171 | return nfit_res->buf; |
| 1166 | 1172 | err: |
| 1173 | + if (*dma && size >= DIMM_SIZE) | |
| 1174 | + gen_pool_free(nfit_pool, *dma, size); | |
| 1167 | 1175 | if (buf) |
| 1168 | 1176 | vfree(buf); |
| 1169 | 1177 | kfree(nfit_res); |
| @@ -1172,9 +1180,16 @@ static void *__test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma, · 2 unmodified lines | ||
| 1172 | 1180 | |
| 1173 | 1181 | static void *test_alloc(struct nfit_test *t, size_t size, dma_addr_t *dma) |
| 1174 | 1182 | { |
| 1183 | + struct genpool_data_align data = { | |
| 1184 | + .align = SZ_128M, | |
| 1185 | + }; | |
| 1175 | 1186 | void *buf = vmalloc(size); |
| 1176 | 1187 | |
| 1177 | − *dma = (unsigned long) buf; | |
| 1188 | + if (size >= DIMM_SIZE) | |
| 1189 | + *dma = gen_pool_alloc_algo(nfit_pool, size, | |
| 1190 | + gen_pool_first_fit_align, &data); | |
| 1191 | + else | |
| 1192 | + *dma = (unsigned long) buf; | |
| 1178 | 1193 | return __test_alloc(t, size, dma, buf); |
| 1179 | 1194 | } |
| 1180 | 1195 | |
| @@ -2839,6 +2854,17 @@ static __init int nfit_test_init(void) · 1658 unmodified lines | ||
| 2839 | 2854 | goto err_register; |
| 2840 | 2855 | } |
| 2841 | 2856 | |
| 2857 | + nfit_pool = gen_pool_create(ilog2(SZ_4M), NUMA_NO_NODE); | |
| 2858 | + if (!nfit_pool) { | |
| 2859 | + rc = -ENOMEM; | |
| 2860 | + goto err_register; | |
| 2861 | + } | |
| 2862 | + | |
| 2863 | + if (gen_pool_add(nfit_pool, SZ_4G, SZ_4G, NUMA_NO_NODE)) { | |
| 2864 | + rc = -ENOMEM; | |
| 2865 | + goto err_register; | |
| 2866 | + } | |
| 2867 | + | |
| 2842 | 2868 | for (i = 0; i < NUM_NFITS; i++) { |
| 2843 | 2869 | struct nfit_test *nfit_test; |
| 2844 | 2870 | struct platform_device *pdev; |
| @@ -2894,6 +2920,9 @@ static __init int nfit_test_init(void) · 49 unmodified lines | ||
| 2894 | 2920 | return 0; |
| 2895 | 2921 | |
| 2896 | 2922 | err_register: |
| 2923 | + if (nfit_pool) | |
| 2924 | + gen_pool_destroy(nfit_pool); | |
| 2925 | + | |
| 2897 | 2926 | destroy_workqueue(nfit_wq); |
| 2898 | 2927 | for (i = 0; i < NUM_NFITS; i++) |
| 2899 | 2928 | if (instances[i]) |
| @@ -2917,6 +2946,8 @@ static __exit void nfit_test_exit(void) · 17 unmodified lines | ||
| 2917 | 2946 | platform_driver_unregister(&nfit_test_driver); |
| 2918 | 2947 | nfit_test_teardown(); |
| 2919 | 2948 | |
| 2949 | + gen_pool_destroy(nfit_pool); | |
| 2950 | + | |
| 2920 | 2951 | for (i = 0; i < NUM_NFITS; i++) |
| 2921 | 2952 | put_device(&instances[i]->pdev.dev); |
| 2922 | 2953 | class_destroy(nfit_test_dimm); |