1 // SPDX-License-Identifier: GPL-2.0 2 #include <linux/platform_device.h> 3 #include <linux/memregion.h> 4 #include <linux/module.h> 5 #include <linux/dax.h> 6 #include <cxl/cxl.h> 7 #include "../bus.h" 8 9 static bool region_idle; 10 module_param_named(region_idle, region_idle, bool, 0644); 11 12 static int dax_hmem_probe(struct platform_device *pdev) 13 { 14 unsigned long flags = IORESOURCE_DAX_KMEM; 15 struct device *dev = &pdev->dev; 16 struct dax_region *dax_region; 17 struct memregion_info *mri; 18 struct dev_dax_data data; 19 20 /* 21 * @region_idle == true indicates that an administrative agent 22 * wants to manipulate the range partitioning before the devices 23 * are created, so do not send them to the dax_kmem driver by 24 * default. 25 */ 26 if (region_idle) 27 flags = 0; 28 29 mri = dev->platform_data; 30 dax_region = alloc_dax_region(dev, pdev->id, &mri->range, 31 mri->target_node, PMD_SIZE, flags); 32 if (!dax_region) 33 return -ENOMEM; 34 35 data = (struct dev_dax_data) { 36 .dax_region = dax_region, 37 .id = -1, 38 .size = region_idle ? 0 : range_len(&mri->range), 39 .memmap_on_memory = false, 40 }; 41 42 return PTR_ERR_OR_ZERO(devm_create_dev_dax(&data)); 43 } 44 45 static struct platform_driver dax_hmem_driver = { 46 .probe = dax_hmem_probe, 47 .driver = { 48 .name = "hmem", 49 }, 50 }; 51 52 static void release_memregion(void *data) 53 { 54 memregion_free((long) data); 55 } 56 57 static void release_hmem(void *pdev) 58 { 59 platform_device_unregister(pdev); 60 } 61 62 struct dax_defer_work { 63 struct platform_device *pdev; 64 struct work_struct work; 65 }; 66 67 static void process_defer_work(struct work_struct *w); 68 69 static struct dax_defer_work dax_hmem_work = { 70 .work = __WORK_INITIALIZER(dax_hmem_work.work, process_defer_work), 71 }; 72 73 void dax_hmem_flush_work(void) 74 { 75 flush_work(&dax_hmem_work.work); 76 } 77 EXPORT_SYMBOL_GPL(dax_hmem_flush_work); 78 79 static int __hmem_register_device(struct device *host, int target_nid, 80 const struct resource *res) 81 { 82 struct platform_device *pdev; 83 struct memregion_info info; 84 long id; 85 int rc; 86 87 rc = region_intersects_soft_reserve(res->start, resource_size(res)); 88 if (rc != REGION_INTERSECTS) 89 return 0; 90 91 /* TODO: Add Soft-Reserved memory back to iomem */ 92 93 id = memregion_alloc(GFP_KERNEL); 94 if (id < 0) { 95 dev_err(host, "memregion allocation failure for %pr\n", res); 96 return -ENOMEM; 97 } 98 rc = devm_add_action_or_reset(host, release_memregion, (void *) id); 99 if (rc) 100 return rc; 101 102 pdev = platform_device_alloc("hmem", id); 103 if (!pdev) { 104 dev_err(host, "device allocation failure for %pr\n", res); 105 return -ENOMEM; 106 } 107 108 pdev->dev.numa_node = numa_map_to_online_node(target_nid); 109 info = (struct memregion_info) { 110 .target_node = target_nid, 111 .range = { 112 .start = res->start, 113 .end = res->end, 114 }, 115 }; 116 rc = platform_device_add_data(pdev, &info, sizeof(info)); 117 if (rc < 0) { 118 dev_err(host, "memregion_info allocation failure for %pr\n", 119 res); 120 goto out_put; 121 } 122 123 rc = platform_device_add(pdev); 124 if (rc < 0) { 125 dev_err(host, "%s add failed for %pr\n", dev_name(&pdev->dev), 126 res); 127 goto out_put; 128 } 129 130 return devm_add_action_or_reset(host, release_hmem, pdev); 131 132 out_put: 133 platform_device_put(pdev); 134 return rc; 135 } 136 137 static int hmem_register_device(struct device *host, int target_nid, 138 const struct resource *res) 139 { 140 if (IS_ENABLED(CONFIG_DEV_DAX_CXL) && 141 region_intersects(res->start, resource_size(res), IORESOURCE_MEM, 142 IORES_DESC_CXL) != REGION_DISJOINT) { 143 if (!dax_hmem_initial_probe) { 144 dev_dbg(host, "await CXL initial probe: %pr\n", res); 145 queue_work(system_long_wq, &dax_hmem_work.work); 146 return 0; 147 } 148 dev_dbg(host, "deferring range to CXL: %pr\n", res); 149 return 0; 150 } 151 152 return __hmem_register_device(host, target_nid, res); 153 } 154 155 static int hmem_register_cxl_device(struct device *host, int target_nid, 156 const struct resource *res) 157 { 158 if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM, 159 IORES_DESC_CXL) == REGION_DISJOINT) 160 return 0; 161 162 if (cxl_region_contains_resource((struct resource *)res)) { 163 dev_dbg(host, "CXL claims resource, dropping: %pr\n", res); 164 return 0; 165 } 166 167 dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res); 168 return __hmem_register_device(host, target_nid, res); 169 } 170 171 static void process_defer_work(struct work_struct *w) 172 { 173 struct dax_defer_work *work = container_of(w, typeof(*work), work); 174 struct platform_device *pdev; 175 176 if (!work->pdev) 177 return; 178 179 pdev = work->pdev; 180 181 /* Relies on cxl_acpi and cxl_pci having had a chance to load */ 182 wait_for_device_probe(); 183 184 guard(device)(&pdev->dev); 185 if (!pdev->dev.driver) 186 return; 187 188 if (!dax_hmem_initial_probe) { 189 dax_hmem_initial_probe = true; 190 walk_hmem_resources(&pdev->dev, hmem_register_cxl_device); 191 } 192 } 193 194 static int dax_hmem_platform_probe(struct platform_device *pdev) 195 { 196 if (work_pending(&dax_hmem_work.work)) 197 return -EBUSY; 198 199 if (!dax_hmem_work.pdev) 200 dax_hmem_work.pdev = 201 to_platform_device(get_device(&pdev->dev)); 202 203 return walk_hmem_resources(&pdev->dev, hmem_register_device); 204 } 205 206 static struct platform_driver dax_hmem_platform_driver = { 207 .probe = dax_hmem_platform_probe, 208 .driver = { 209 .name = "hmem_platform", 210 }, 211 }; 212 213 static __init int dax_hmem_init(void) 214 { 215 int rc; 216 217 /* 218 * Ensure that cxl_acpi and cxl_pci have a chance to kick off 219 * CXL topology discovery at least once before scanning the 220 * iomem resource tree for IORES_DESC_CXL resources. 221 */ 222 if (IS_ENABLED(CONFIG_DEV_DAX_CXL)) { 223 request_module("cxl_acpi"); 224 request_module("cxl_pci"); 225 } 226 227 rc = platform_driver_register(&dax_hmem_platform_driver); 228 if (rc) 229 return rc; 230 231 rc = platform_driver_register(&dax_hmem_driver); 232 if (rc) 233 platform_driver_unregister(&dax_hmem_platform_driver); 234 235 return rc; 236 } 237 238 static __exit void dax_hmem_exit(void) 239 { 240 if (dax_hmem_work.pdev) { 241 flush_work(&dax_hmem_work.work); 242 put_device(&dax_hmem_work.pdev->dev); 243 } 244 245 platform_driver_unregister(&dax_hmem_driver); 246 platform_driver_unregister(&dax_hmem_platform_driver); 247 } 248 249 module_init(dax_hmem_init); 250 module_exit(dax_hmem_exit); 251 252 MODULE_ALIAS("platform:hmem*"); 253 MODULE_ALIAS("platform:hmem_platform*"); 254 MODULE_DESCRIPTION("HMEM DAX: direct access to 'specific purpose' memory"); 255 MODULE_LICENSE("GPL v2"); 256 MODULE_AUTHOR("Intel Corporation"); 257