Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cxl/mem: Add the cxl_mem driver

At this point the subsystem can enumerate all CXL ports (CXL.mem decode
resources in upstream switch ports and host bridges) in a system. The
last mile is connecting those ports to endpoints.

The cxl_mem driver connects an endpoint device to the platform CXL.mem
protoctol decode-topology. At ->probe() time it walks its
device-topology-ancestry and adds a CXL Port object at every Upstream
Port hop until it gets to CXL root. The CXL root object is only present
after a platform firmware driver registers platform CXL resources. For
ACPI based platform this is managed by the ACPI0017 device and the
cxl_acpi driver.

The ports are registered such that disabling a given port automatically
unregisters all descendant ports, and the chain can only be registered
after the root is established.

Given ACPI device scanning may run asynchronously compared to PCI device
scanning the root driver is tasked with rescanning the bus after the
root successfully probes.

Conversely if any ports in a chain between the root and an endpoint
becomes disconnected it subsequently triggers the endpoint to
unregister. Given lock depenedencies the endpoint unregistration happens
in a workqueue asynchronously. If userspace cares about synchronizing
delayed work after port events the /sys/bus/cxl/flush attribute is
available for that purpose.

Reported-by: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Ben Widawsky <ben.widawsky@intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
[djbw: clarify changelog, rework hotplug support]
Link: https://lore.kernel.org/r/164398782997.903003.9725273241627693186.stgit@dwillia2-desk3.amr.corp.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

authored by

Ben Widawsky and committed by
Dan Williams
8dd2bc0f 2703c16c

+425 -5
+9
Documentation/ABI/testing/sysfs-bus-cxl
··· 1 + What: /sys/bus/cxl/flush 2 + Date: Januarry, 2022 3 + KernelVersion: v5.18 4 + Contact: linux-cxl@vger.kernel.org 5 + Description: 6 + (WO) If userspace manually unbinds a port the kernel schedules 7 + all descendant memdevs for unbind. Writing '1' to this attribute 8 + flushes that work. 9 + 1 10 What: /sys/bus/cxl/devices/memX/firmware_version 2 11 Date: December, 2020 3 12 KernelVersion: v5.12
+9
Documentation/driver-api/cxl/memory-devices.rst
··· 325 325 .. kernel-doc:: drivers/cxl/pci.c 326 326 :internal: 327 327 328 + .. kernel-doc:: drivers/cxl/mem.c 329 + :doc: cxl mem 330 + 328 331 CXL Port 329 332 -------- 330 333 .. kernel-doc:: drivers/cxl/port.c ··· 345 342 :doc: cxl core 346 343 347 344 .. kernel-doc:: drivers/cxl/core/port.c 345 + :identifiers: 346 + 347 + .. kernel-doc:: drivers/cxl/core/pci.c 348 + :doc: cxl core pci 349 + 350 + .. kernel-doc:: drivers/cxl/core/pci.c 348 351 :identifiers: 349 352 350 353 .. kernel-doc:: drivers/cxl/core/pmem.c
+16
drivers/cxl/Kconfig
··· 78 78 79 79 If unsure say 'm'. 80 80 81 + config CXL_MEM 82 + tristate "CXL: Memory Expansion" 83 + depends on CXL_PCI 84 + default CXL_BUS 85 + help 86 + The CXL.mem protocol allows a device to act as a provider of "System 87 + RAM" and/or "Persistent Memory" that is fully coherent as if the 88 + memory were attached to the typical CPU memory controller. This is 89 + known as HDM "Host-managed Device Memory". 90 + 91 + Say 'y/m' to enable a driver that will attach to CXL.mem devices for 92 + memory expansion and control of HDM. See Chapter 9.13 in the CXL 2.0 93 + specification for a detailed description of HDM. 94 + 95 + If unsure say 'm'. 96 + 81 97 config CXL_PORT 82 98 default CXL_BUS 83 99 tristate
+2
drivers/cxl/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 obj-$(CONFIG_CXL_BUS) += core/ 3 3 obj-$(CONFIG_CXL_PCI) += cxl_pci.o 4 + obj-$(CONFIG_CXL_MEM) += cxl_mem.o 4 5 obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o 5 6 obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o 6 7 obj-$(CONFIG_CXL_PORT) += cxl_port.o 7 8 9 + cxl_mem-y := mem.o 8 10 cxl_pci-y := pci.o 9 11 cxl_acpi-y := acpi.o 10 12 cxl_pmem-y := pmem.o
+2 -1
drivers/cxl/acpi.c
··· 314 314 if (rc < 0) 315 315 return rc; 316 316 317 - return 0; 317 + /* In case PCI is scanned before ACPI re-trigger memdev attach */ 318 + return cxl_bus_rescan(); 318 319 } 319 320 320 321 static const struct acpi_device_id cxl_acpi_ids[] = {
+16
drivers/cxl/core/memdev.c
··· 162 162 .groups = cxl_memdev_attribute_groups, 163 163 }; 164 164 165 + bool is_cxl_memdev(struct device *dev) 166 + { 167 + return dev->type == &cxl_memdev_type; 168 + } 169 + EXPORT_SYMBOL_NS_GPL(is_cxl_memdev, CXL); 170 + 165 171 /** 166 172 * set_exclusive_cxl_commands() - atomically disable user cxl commands 167 173 * @cxlds: The device state to operate on ··· 219 213 put_device(dev); 220 214 } 221 215 216 + static void detach_memdev(struct work_struct *work) 217 + { 218 + struct cxl_memdev *cxlmd; 219 + 220 + cxlmd = container_of(work, typeof(*cxlmd), detach_work); 221 + device_release_driver(&cxlmd->dev); 222 + put_device(&cxlmd->dev); 223 + } 224 + 222 225 static struct cxl_memdev *cxl_memdev_alloc(struct cxl_dev_state *cxlds, 223 226 const struct file_operations *fops) 224 227 { ··· 252 237 dev->devt = MKDEV(cxl_mem_major, cxlmd->id); 253 238 dev->type = &cxl_memdev_type; 254 239 device_set_pm_not_required(dev); 240 + INIT_WORK(&cxlmd->detach_work, detach_memdev); 255 241 256 242 cdev = &cxlmd->cdev; 257 243 cdev_init(cdev, fops);
+101 -4
drivers/cxl/core/port.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0-only 2 2 /* Copyright(c) 2020 Intel Corporation. All rights reserved. */ 3 3 #include <linux/io-64-nonatomic-lo-hi.h> 4 + #include <linux/workqueue.h> 4 5 #include <linux/device.h> 5 6 #include <linux/module.h> 6 7 #include <linux/pci.h> ··· 47 46 return CXL_DEVICE_ROOT; 48 47 return CXL_DEVICE_PORT; 49 48 } 49 + if (is_cxl_memdev(dev)) 50 + return CXL_DEVICE_MEMORY_EXPANDER; 50 51 return 0; 51 52 } 52 53 ··· 321 318 { 322 319 struct cxl_port *port = _port; 323 320 324 - if (!is_cxl_root(port)) 321 + if (!is_cxl_root(port)) { 325 322 device_lock_assert(port->dev.parent); 323 + port->uport = NULL; 324 + } 326 325 327 326 device_unregister(&port->dev); 328 327 } ··· 415 410 if (parent_port) 416 411 port->depth = parent_port->depth + 1; 417 412 dev = &port->dev; 418 - if (parent_port) 413 + if (is_cxl_memdev(uport)) 414 + rc = dev_set_name(dev, "endpoint%d", port->id); 415 + else if (parent_port) 419 416 rc = dev_set_name(dev, "port%d", port->id); 420 417 else 421 418 rc = dev_set_name(dev, "root%d", port->id); ··· 797 790 return NULL; 798 791 } 799 792 793 + static void delete_endpoint(void *data) 794 + { 795 + struct cxl_memdev *cxlmd = data; 796 + struct cxl_port *endpoint = dev_get_drvdata(&cxlmd->dev); 797 + struct cxl_port *parent_port; 798 + struct device *parent; 799 + 800 + parent_port = cxl_mem_find_port(cxlmd); 801 + if (!parent_port) 802 + return; 803 + parent = &parent_port->dev; 804 + 805 + cxl_device_lock(parent); 806 + if (parent->driver && endpoint->uport) { 807 + devm_release_action(parent, cxl_unlink_uport, endpoint); 808 + devm_release_action(parent, unregister_port, endpoint); 809 + } 810 + cxl_device_unlock(parent); 811 + put_device(parent); 812 + put_device(&endpoint->dev); 813 + } 814 + 815 + int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint) 816 + { 817 + struct device *dev = &cxlmd->dev; 818 + 819 + get_device(&endpoint->dev); 820 + dev_set_drvdata(dev, endpoint); 821 + return devm_add_action_or_reset(dev, delete_endpoint, cxlmd); 822 + } 823 + EXPORT_SYMBOL_NS_GPL(cxl_endpoint_autoremove, CXL); 824 + 800 825 /* 801 826 * The natural end of life of a non-root 'cxl_port' is when its parent port goes 802 827 * through a ->remove() event ("top-down" unregistration). The unnatural trigger ··· 1072 1033 return 0; 1073 1034 } 1074 1035 EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_ports, CXL); 1036 + 1037 + struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd) 1038 + { 1039 + return find_cxl_port(grandparent(&cxlmd->dev)); 1040 + } 1041 + EXPORT_SYMBOL_NS_GPL(cxl_mem_find_port, CXL); 1075 1042 1076 1043 struct cxl_dport *cxl_find_dport_by_dev(struct cxl_port *port, 1077 1044 const struct device *dev) ··· 1397 1352 cxl_nested_unlock(dev); 1398 1353 } 1399 1354 1355 + static struct workqueue_struct *cxl_bus_wq; 1356 + 1357 + int cxl_bus_rescan(void) 1358 + { 1359 + return bus_rescan_devices(&cxl_bus_type); 1360 + } 1361 + EXPORT_SYMBOL_NS_GPL(cxl_bus_rescan, CXL); 1362 + 1363 + bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd) 1364 + { 1365 + return queue_work(cxl_bus_wq, &cxlmd->detach_work); 1366 + } 1367 + EXPORT_SYMBOL_NS_GPL(schedule_cxl_memdev_detach, CXL); 1368 + 1369 + /* for user tooling to ensure port disable work has completed */ 1370 + static ssize_t flush_store(struct bus_type *bus, const char *buf, size_t count) 1371 + { 1372 + if (sysfs_streq(buf, "1")) { 1373 + flush_workqueue(cxl_bus_wq); 1374 + return count; 1375 + } 1376 + 1377 + return -EINVAL; 1378 + } 1379 + 1380 + static BUS_ATTR_WO(flush); 1381 + 1382 + static struct attribute *cxl_bus_attributes[] = { 1383 + &bus_attr_flush.attr, 1384 + NULL, 1385 + }; 1386 + 1387 + static struct attribute_group cxl_bus_attribute_group = { 1388 + .attrs = cxl_bus_attributes, 1389 + }; 1390 + 1391 + static const struct attribute_group *cxl_bus_attribute_groups[] = { 1392 + &cxl_bus_attribute_group, 1393 + NULL, 1394 + }; 1395 + 1400 1396 struct bus_type cxl_bus_type = { 1401 1397 .name = "cxl", 1402 1398 .uevent = cxl_bus_uevent, 1403 1399 .match = cxl_bus_match, 1404 1400 .probe = cxl_bus_probe, 1405 1401 .remove = cxl_bus_remove, 1402 + .bus_groups = cxl_bus_attribute_groups, 1406 1403 }; 1407 1404 EXPORT_SYMBOL_NS_GPL(cxl_bus_type, CXL); 1408 1405 ··· 1458 1371 if (rc) 1459 1372 return rc; 1460 1373 1374 + cxl_bus_wq = alloc_ordered_workqueue("cxl_port", 0); 1375 + if (!cxl_bus_wq) { 1376 + rc = -ENOMEM; 1377 + goto err_wq; 1378 + } 1379 + 1461 1380 rc = bus_register(&cxl_bus_type); 1462 1381 if (rc) 1463 - goto err; 1382 + goto err_bus; 1383 + 1464 1384 return 0; 1465 1385 1466 - err: 1386 + err_bus: 1387 + destroy_workqueue(cxl_bus_wq); 1388 + err_wq: 1467 1389 cxl_memdev_exit(); 1468 1390 cxl_mbox_exit(); 1469 1391 return rc; ··· 1481 1385 static void cxl_core_exit(void) 1482 1386 { 1483 1387 bus_unregister(&cxl_bus_type); 1388 + destroy_workqueue(cxl_bus_wq); 1484 1389 cxl_memdev_exit(); 1485 1390 cxl_mbox_exit(); 1486 1391 }
+6
drivers/cxl/cxl.h
··· 328 328 struct cxl_port *parent_port); 329 329 struct cxl_port *find_cxl_root(struct device *dev); 330 330 int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd); 331 + int cxl_bus_rescan(void); 332 + struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd); 333 + bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd); 331 334 332 335 struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port, 333 336 struct device *dport, int port_id, ··· 348 345 int cxl_decoder_add(struct cxl_decoder *cxld, int *target_map); 349 346 int cxl_decoder_add_locked(struct cxl_decoder *cxld, int *target_map); 350 347 int cxl_decoder_autoremove(struct device *host, struct cxl_decoder *cxld); 348 + int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint); 349 + 351 350 struct cxl_hdm; 352 351 struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port); 353 352 int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm); ··· 382 377 #define CXL_DEVICE_NVDIMM 2 383 378 #define CXL_DEVICE_PORT 3 384 379 #define CXL_DEVICE_ROOT 4 380 + #define CXL_DEVICE_MEMORY_EXPANDER 5 385 381 386 382 #define MODULE_ALIAS_CXL(type) MODULE_ALIAS("cxl:t" __stringify(type) "*") 387 383 #define CXL_MODALIAS_FMT "cxl:t%d"
+8
drivers/cxl/cxlmem.h
··· 34 34 * @dev: driver core device object 35 35 * @cdev: char dev core object for ioctl operations 36 36 * @cxlds: The device state backing this device 37 + * @detach_work: active memdev lost a port in its ancestry 37 38 * @id: id number of this memdev instance. 38 39 */ 39 40 struct cxl_memdev { 40 41 struct device dev; 41 42 struct cdev cdev; 42 43 struct cxl_dev_state *cxlds; 44 + struct work_struct detach_work; 43 45 int id; 44 46 }; 45 47 46 48 static inline struct cxl_memdev *to_cxl_memdev(struct device *dev) 47 49 { 48 50 return container_of(dev, struct cxl_memdev, dev); 51 + } 52 + 53 + bool is_cxl_memdev(struct device *dev); 54 + static inline bool is_cxl_endpoint(struct cxl_port *port) 55 + { 56 + return is_cxl_memdev(port->uport); 49 57 } 50 58 51 59 struct cxl_memdev *devm_cxl_add_memdev(struct cxl_dev_state *cxlds);
+228
drivers/cxl/mem.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* Copyright(c) 2022 Intel Corporation. All rights reserved. */ 3 + #include <linux/device.h> 4 + #include <linux/module.h> 5 + #include <linux/pci.h> 6 + 7 + #include "cxlmem.h" 8 + #include "cxlpci.h" 9 + 10 + /** 11 + * DOC: cxl mem 12 + * 13 + * CXL memory endpoint devices and switches are CXL capable devices that are 14 + * participating in CXL.mem protocol. Their functionality builds on top of the 15 + * CXL.io protocol that allows enumerating and configuring components via 16 + * standard PCI mechanisms. 17 + * 18 + * The cxl_mem driver owns kicking off the enumeration of this CXL.mem 19 + * capability. With the detection of a CXL capable endpoint, the driver will 20 + * walk up to find the platform specific port it is connected to, and determine 21 + * if there are intervening switches in the path. If there are switches, a 22 + * secondary action is to enumerate those (implemented in cxl_core). Finally the 23 + * cxl_mem driver adds the device it is bound to as a CXL endpoint-port for use 24 + * in higher level operations. 25 + */ 26 + 27 + static int wait_for_media(struct cxl_memdev *cxlmd) 28 + { 29 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 30 + struct cxl_endpoint_dvsec_info *info = &cxlds->info; 31 + int rc; 32 + 33 + if (!info->mem_enabled) 34 + return -EBUSY; 35 + 36 + rc = cxlds->wait_media_ready(cxlds); 37 + if (rc) 38 + return rc; 39 + 40 + /* 41 + * We know the device is active, and enabled, if any ranges are non-zero 42 + * we'll need to check later before adding the port since that owns the 43 + * HDM decoder registers. 44 + */ 45 + return 0; 46 + } 47 + 48 + static int create_endpoint(struct cxl_memdev *cxlmd, 49 + struct cxl_port *parent_port) 50 + { 51 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 52 + struct cxl_port *endpoint; 53 + 54 + endpoint = devm_cxl_add_port(&parent_port->dev, &cxlmd->dev, 55 + cxlds->component_reg_phys, parent_port); 56 + if (IS_ERR(endpoint)) 57 + return PTR_ERR(endpoint); 58 + 59 + dev_dbg(&cxlmd->dev, "add: %s\n", dev_name(&endpoint->dev)); 60 + 61 + if (!endpoint->dev.driver) { 62 + dev_err(&cxlmd->dev, "%s failed probe\n", 63 + dev_name(&endpoint->dev)); 64 + return -ENXIO; 65 + } 66 + 67 + return cxl_endpoint_autoremove(cxlmd, endpoint); 68 + } 69 + 70 + /** 71 + * cxl_dvsec_decode_init() - Setup HDM decoding for the endpoint 72 + * @cxlds: Device state 73 + * 74 + * Additionally, enables global HDM decoding. Warning: don't call this outside 75 + * of probe. Once probe is complete, the port driver owns all access to the HDM 76 + * decoder registers. 77 + * 78 + * Returns: false if DVSEC Ranges are being used instead of HDM 79 + * decoders, or if it can not be determined if DVSEC Ranges are in use. 80 + * Otherwise, returns true. 81 + */ 82 + __mock bool cxl_dvsec_decode_init(struct cxl_dev_state *cxlds) 83 + { 84 + struct cxl_endpoint_dvsec_info *info = &cxlds->info; 85 + struct cxl_register_map map; 86 + struct cxl_component_reg_map *cmap = &map.component_map; 87 + bool global_enable, do_hdm_init = false; 88 + void __iomem *crb; 89 + u32 global_ctrl; 90 + 91 + /* map hdm decoder */ 92 + crb = ioremap(cxlds->component_reg_phys, CXL_COMPONENT_REG_BLOCK_SIZE); 93 + if (!crb) { 94 + dev_dbg(cxlds->dev, "Failed to map component registers\n"); 95 + return false; 96 + } 97 + 98 + cxl_probe_component_regs(cxlds->dev, crb, cmap); 99 + if (!cmap->hdm_decoder.valid) { 100 + dev_dbg(cxlds->dev, "Invalid HDM decoder registers\n"); 101 + goto out; 102 + } 103 + 104 + global_ctrl = readl(crb + cmap->hdm_decoder.offset + 105 + CXL_HDM_DECODER_CTRL_OFFSET); 106 + global_enable = global_ctrl & CXL_HDM_DECODER_ENABLE; 107 + if (!global_enable && info->ranges) { 108 + dev_dbg(cxlds->dev, 109 + "DVSEC ranges already programmed and HDM decoders not enabled.\n"); 110 + goto out; 111 + } 112 + 113 + do_hdm_init = true; 114 + 115 + /* 116 + * Permanently (for this boot at least) opt the device into HDM 117 + * operation. Individual HDM decoders still need to be enabled after 118 + * this point. 119 + */ 120 + if (!global_enable) { 121 + dev_dbg(cxlds->dev, "Enabling HDM decode\n"); 122 + writel(global_ctrl | CXL_HDM_DECODER_ENABLE, 123 + crb + cmap->hdm_decoder.offset + 124 + CXL_HDM_DECODER_CTRL_OFFSET); 125 + } 126 + 127 + out: 128 + iounmap(crb); 129 + return do_hdm_init; 130 + } 131 + 132 + static int cxl_mem_probe(struct device *dev) 133 + { 134 + struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 135 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 136 + struct cxl_port *parent_port; 137 + int rc; 138 + 139 + /* 140 + * Someone is trying to reattach this device after it lost its port 141 + * connection (an endpoint port previously registered by this memdev was 142 + * disabled). This racy check is ok because if the port is still gone, 143 + * no harm done, and if the port hierarchy comes back it will re-trigger 144 + * this probe. Port rescan and memdev detach work share the same 145 + * single-threaded workqueue. 146 + */ 147 + if (work_pending(&cxlmd->detach_work)) 148 + return -EBUSY; 149 + 150 + rc = wait_for_media(cxlmd); 151 + if (rc) { 152 + dev_err(dev, "Media not active (%d)\n", rc); 153 + return rc; 154 + } 155 + 156 + /* 157 + * If DVSEC ranges are being used instead of HDM decoder registers there 158 + * is no use in trying to manage those. 159 + */ 160 + if (!cxl_dvsec_decode_init(cxlds)) { 161 + struct cxl_endpoint_dvsec_info *info = &cxlds->info; 162 + int i; 163 + 164 + /* */ 165 + for (i = 0; i < 2; i++) { 166 + u64 base, size; 167 + 168 + /* 169 + * Give a nice warning to the user that BIOS has really 170 + * botched things for them if it didn't place DVSEC 171 + * ranges in the memory map. 172 + */ 173 + base = info->dvsec_range[i].start; 174 + size = range_len(&info->dvsec_range[i]); 175 + if (size && !region_intersects(base, size, 176 + IORESOURCE_SYSTEM_RAM, 177 + IORES_DESC_NONE)) { 178 + dev_err(dev, 179 + "DVSEC range %#llx-%#llx must be reserved by BIOS, but isn't\n", 180 + base, base + size - 1); 181 + } 182 + } 183 + dev_err(dev, 184 + "Active DVSEC range registers in use. Will not bind.\n"); 185 + return -EBUSY; 186 + } 187 + 188 + rc = devm_cxl_enumerate_ports(cxlmd); 189 + if (rc) 190 + return rc; 191 + 192 + parent_port = cxl_mem_find_port(cxlmd); 193 + if (!parent_port) { 194 + dev_err(dev, "CXL port topology not found\n"); 195 + return -ENXIO; 196 + } 197 + 198 + cxl_device_lock(&parent_port->dev); 199 + if (!parent_port->dev.driver) { 200 + dev_err(dev, "CXL port topology %s not enabled\n", 201 + dev_name(&parent_port->dev)); 202 + rc = -ENXIO; 203 + goto out; 204 + } 205 + 206 + rc = create_endpoint(cxlmd, parent_port); 207 + out: 208 + cxl_device_unlock(&parent_port->dev); 209 + put_device(&parent_port->dev); 210 + return rc; 211 + } 212 + 213 + static struct cxl_driver cxl_mem_driver = { 214 + .name = "cxl_mem", 215 + .probe = cxl_mem_probe, 216 + .id = CXL_DEVICE_MEMORY_EXPANDER, 217 + }; 218 + 219 + module_cxl_driver(cxl_mem_driver); 220 + 221 + MODULE_LICENSE("GPL v2"); 222 + MODULE_IMPORT_NS(CXL); 223 + MODULE_ALIAS_CXL(CXL_DEVICE_MEMORY_EXPANDER); 224 + /* 225 + * create_endpoint() wants to validate port driver attach immediately after 226 + * endpoint registration. 227 + */ 228 + MODULE_SOFTDEP("pre: cxl_port");
+12
drivers/cxl/port.c
··· 25 25 * PCIe topology. 26 26 */ 27 27 28 + static void schedule_detach(void *cxlmd) 29 + { 30 + schedule_cxl_memdev_detach(cxlmd); 31 + } 32 + 28 33 static int cxl_port_probe(struct device *dev) 29 34 { 30 35 struct cxl_port *port = to_cxl_port(dev); 31 36 struct cxl_hdm *cxlhdm; 32 37 int rc; 38 + 39 + if (is_cxl_endpoint(port)) { 40 + struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport); 41 + 42 + get_device(&cxlmd->dev); 43 + return devm_add_action_or_reset(dev, schedule_detach, cxlmd); 44 + } 33 45 34 46 rc = devm_cxl_port_enumerate_dports(port); 35 47 if (rc < 0)
+6
tools/testing/cxl/Kbuild
··· 31 31 cxl_port-y := $(CXL_SRC)/port.o 32 32 cxl_port-y += config_check.o 33 33 34 + obj-m += cxl_mem.o 35 + 36 + cxl_mem-y := $(CXL_SRC)/mem.o 37 + cxl_mem-y += mock_mem.o 38 + cxl_mem-y += config_check.o 39 + 34 40 obj-m += cxl_core.o 35 41 36 42 cxl_core-y := $(CXL_CORE_SRC)/port.o
+10
tools/testing/cxl/mock_mem.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* Copyright(c) 2022 Intel Corporation. All rights reserved. */ 3 + 4 + #include <linux/types.h> 5 + 6 + struct cxl_dev_state; 7 + bool cxl_dvsec_decode_init(struct cxl_dev_state *cxlds) 8 + { 9 + return true; 10 + }