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 static struct workqueue_struct *dax_hmem_wq; 63 64 void dax_hmem_flush_work(void) 65 { 66 flush_workqueue(dax_hmem_wq); 67 } 68 EXPORT_SYMBOL_FOR_MODULES(dax_hmem_flush_work, "dax_cxl"); 69 70 static int __hmem_register_device(struct device *host, int target_nid, 71 const struct resource *res) 72 { 73 struct platform_device *pdev; 74 struct memregion_info info; 75 long id; 76 int rc; 77 78 rc = region_intersects_soft_reserve(res->start, resource_size(res)); 79 if (rc != REGION_INTERSECTS) 80 return 0; 81 82 /* TODO: Add Soft-Reserved memory back to iomem */ 83 84 id = memregion_alloc(GFP_KERNEL); 85 if (id < 0) { 86 dev_err(host, "memregion allocation failure for %pr\n", res); 87 return -ENOMEM; 88 } 89 rc = devm_add_action_or_reset(host, release_memregion, (void *) id); 90 if (rc) 91 return rc; 92 93 pdev = platform_device_alloc("hmem", id); 94 if (!pdev) { 95 dev_err(host, "device allocation failure for %pr\n", res); 96 return -ENOMEM; 97 } 98 99 pdev->dev.parent = host; 100 pdev->dev.numa_node = numa_map_to_online_node(target_nid); 101 info = (struct memregion_info) { 102 .target_node = target_nid, 103 .range = { 104 .start = res->start, 105 .end = res->end, 106 }, 107 }; 108 rc = platform_device_add_data(pdev, &info, sizeof(info)); 109 if (rc < 0) { 110 dev_err(host, "memregion_info allocation failure for %pr\n", 111 res); 112 goto out_put; 113 } 114 115 rc = platform_device_add(pdev); 116 if (rc < 0) { 117 dev_err(host, "%s add failed for %pr\n", dev_name(&pdev->dev), 118 res); 119 goto out_put; 120 } 121 122 return devm_add_action_or_reset(host, release_hmem, pdev); 123 124 out_put: 125 platform_device_put(pdev); 126 return rc; 127 } 128 129 static int hmem_register_cxl_device(struct device *host, int target_nid, 130 const struct resource *res) 131 { 132 if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM, 133 IORES_DESC_CXL) == REGION_DISJOINT) 134 return 0; 135 136 if (cxl_region_contains_resource(res)) { 137 dev_dbg(host, "CXL claims resource, dropping: %pr\n", res); 138 return 0; 139 } 140 141 dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res); 142 return __hmem_register_device(host, target_nid, res); 143 } 144 145 static void process_defer_work(struct work_struct *w) 146 { 147 struct hmem_platform_device *hpdev = container_of(w, typeof(*hpdev), work); 148 struct device *dev = &hpdev->pdev.dev; 149 150 /* Relies on cxl_acpi and cxl_pci having had a chance to load */ 151 wait_for_device_probe(); 152 153 guard(device)(dev); 154 if (!dev->driver) 155 goto out; 156 157 if (!hpdev->did_probe) { 158 hpdev->did_probe = true; 159 walk_hmem_resources(dev, hmem_register_cxl_device); 160 } 161 out: 162 put_device(dev); 163 } 164 165 static int hmem_register_device(struct device *host, int target_nid, 166 const struct resource *res) 167 { 168 struct platform_device *pdev = to_platform_device(host); 169 struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev); 170 171 if (IS_ENABLED(CONFIG_DEV_DAX_CXL) && 172 region_intersects(res->start, resource_size(res), IORESOURCE_MEM, 173 IORES_DESC_CXL) != REGION_DISJOINT) { 174 if (!hpdev->did_probe) { 175 dev_dbg(host, "await CXL initial probe: %pr\n", res); 176 hpdev->work.func = process_defer_work; 177 get_device(host); 178 if (!queue_work(dax_hmem_wq, &hpdev->work)) 179 put_device(host); 180 return 0; 181 } 182 dev_dbg(host, "deferring range to CXL: %pr\n", res); 183 return 0; 184 } 185 186 return __hmem_register_device(host, target_nid, res); 187 } 188 189 static int dax_hmem_platform_probe(struct platform_device *pdev) 190 { 191 struct hmem_platform_device *hpdev = to_hmem_platform_device(pdev); 192 193 /* queue is only flushed on module unload, fail rebind with pending work */ 194 if (work_pending(&hpdev->work)) 195 return -EBUSY; 196 197 return walk_hmem_resources(&pdev->dev, hmem_register_device); 198 } 199 200 static struct platform_driver dax_hmem_platform_driver = { 201 .probe = dax_hmem_platform_probe, 202 .driver = { 203 .name = "hmem_platform", 204 }, 205 }; 206 207 static __init int dax_hmem_init(void) 208 { 209 int rc; 210 211 /* 212 * Ensure that cxl_acpi and cxl_pci have a chance to kick off 213 * CXL topology discovery at least once before scanning the 214 * iomem resource tree for IORES_DESC_CXL resources. 215 */ 216 if (IS_ENABLED(CONFIG_DEV_DAX_CXL)) { 217 request_module("cxl_acpi"); 218 request_module("cxl_pci"); 219 } 220 221 dax_hmem_wq = alloc_ordered_workqueue("dax_hmem_wq", 0); 222 if (!dax_hmem_wq) 223 return -ENOMEM; 224 225 rc = platform_driver_register(&dax_hmem_platform_driver); 226 if (rc) 227 goto err_platform_driver; 228 229 rc = platform_driver_register(&dax_hmem_driver); 230 if (rc) 231 goto err_driver; 232 233 return 0; 234 235 err_driver: 236 platform_driver_unregister(&dax_hmem_platform_driver); 237 err_platform_driver: 238 destroy_workqueue(dax_hmem_wq); 239 240 return rc; 241 } 242 243 static __exit void dax_hmem_exit(void) 244 { 245 platform_driver_unregister(&dax_hmem_driver); 246 platform_driver_unregister(&dax_hmem_platform_driver); 247 destroy_workqueue(dax_hmem_wq); 248 } 249 250 module_init(dax_hmem_init); 251 module_exit(dax_hmem_exit); 252 253 MODULE_ALIAS("platform:hmem*"); 254 MODULE_ALIAS("platform:hmem_platform*"); 255 MODULE_DESCRIPTION("HMEM DAX: direct access to 'specific purpose' memory"); 256 MODULE_LICENSE("GPL v2"); 257 MODULE_AUTHOR("Intel Corporation"); 258