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

tools/testing/cxl: Introduce a mock memory device + driver

Introduce an emulated device-set plus driver to register CXL memory
devices, 'struct cxl_memdev' instances, in the mock cxl_test topology.
This enables the development of HDM Decoder (Host-managed Device Memory
Decoder) programming flow (region provisioning) in an environment that
can be updated alongside the kernel as it gains more functionality.

Whereas the cxl_pci module looks for CXL memory expanders on the 'pci'
bus, the cxl_mock_mem module attaches to CXL expanders on the platform
bus emitted by cxl_test.

Acked-by: Ben Widawsky <ben.widawsky@intel.com>
Reported-by: kernel test robot <lkp@intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Link: https://lore.kernel.org/r/163116440099.2460985.10692549614409346604.stgit@dwillia2-desk3.amr.corp.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

+359 -6
+3 -3
drivers/cxl/core/pmem.c
··· 51 51 } 52 52 EXPORT_SYMBOL_GPL(to_cxl_nvdimm_bridge); 53 53 54 - static int match_nvdimm_bridge(struct device *dev, const void *data) 54 + __mock int match_nvdimm_bridge(struct device *dev, const void *data) 55 55 { 56 56 return dev->type == &cxl_nvdimm_bridge_type; 57 57 } 58 58 59 - struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(void) 59 + struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd) 60 60 { 61 61 struct device *dev; 62 62 63 - dev = bus_find_device(&cxl_bus_type, NULL, NULL, match_nvdimm_bridge); 63 + dev = bus_find_device(&cxl_bus_type, NULL, cxl_nvd, match_nvdimm_bridge); 64 64 if (!dev) 65 65 return NULL; 66 66 return to_cxl_nvdimm_bridge(dev);
+1 -1
drivers/cxl/cxl.h
··· 327 327 struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev); 328 328 bool is_cxl_nvdimm(struct device *dev); 329 329 int devm_cxl_add_nvdimm(struct device *host, struct cxl_memdev *cxlmd); 330 - struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(void); 330 + struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd); 331 331 332 332 /* 333 333 * Unit test builds overrides this to __weak, find the 'strong' version
+1 -1
drivers/cxl/pmem.c
··· 39 39 struct nvdimm *nvdimm; 40 40 int rc; 41 41 42 - cxl_nvb = cxl_find_nvdimm_bridge(); 42 + cxl_nvb = cxl_find_nvdimm_bridge(cxl_nvd); 43 43 if (!cxl_nvb) 44 44 return -ENXIO; 45 45
+2
tools/testing/cxl/Kbuild
··· 33 33 cxl_core-y += $(CXL_CORE_SRC)/mbox.o 34 34 cxl_core-y += config_check.o 35 35 36 + cxl_core-y += mock_pmem.o 37 + 36 38 obj-m += test/
+24
tools/testing/cxl/mock_pmem.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* Copyright(c) 2021 Intel Corporation. All rights reserved. */ 3 + #include <cxl.h> 4 + #include "test/mock.h" 5 + #include <core/core.h> 6 + 7 + int match_nvdimm_bridge(struct device *dev, const void *data) 8 + { 9 + int index, rc = 0; 10 + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); 11 + const struct cxl_nvdimm *cxl_nvd = data; 12 + 13 + if (ops) { 14 + if (dev->type == &cxl_nvdimm_bridge_type && 15 + (ops->is_mock_dev(dev->parent->parent) == 16 + ops->is_mock_dev(cxl_nvd->dev.parent->parent))) 17 + rc = 1; 18 + } else 19 + rc = dev->type == &cxl_nvdimm_bridge_type; 20 + 21 + put_cxl_mock_ops(index); 22 + 23 + return rc; 24 + }
+4
tools/testing/cxl/test/Kbuild
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 + ccflags-y := -I$(srctree)/drivers/cxl/ 3 + 2 4 obj-m += cxl_test.o 3 5 obj-m += cxl_mock.o 6 + obj-m += cxl_mock_mem.o 4 7 5 8 cxl_test-y := cxl.o 6 9 cxl_mock-y := mock.o 10 + cxl_mock_mem-y := mem.o
+68 -1
tools/testing/cxl/test/cxl.c
··· 17 17 static struct platform_device *cxl_host_bridge[NR_CXL_HOST_BRIDGES]; 18 18 static struct platform_device 19 19 *cxl_root_port[NR_CXL_HOST_BRIDGES * NR_CXL_ROOT_PORTS]; 20 + struct platform_device *cxl_mem[NR_CXL_HOST_BRIDGES * NR_CXL_ROOT_PORTS]; 20 21 21 22 static struct acpi_device acpi0017_mock; 22 23 static struct acpi_device host_bridge[NR_CXL_HOST_BRIDGES] = { ··· 37 36 38 37 static bool is_mock_dev(struct device *dev) 39 38 { 39 + int i; 40 + 41 + for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) 42 + if (dev == &cxl_mem[i]->dev) 43 + return true; 40 44 if (dev == &cxl_acpi->dev) 41 45 return true; 42 46 return false; ··· 411 405 #define SZ_512G (SZ_64G * 8) 412 406 #endif 413 407 408 + static struct platform_device *alloc_memdev(int id) 409 + { 410 + struct resource res[] = { 411 + [0] = { 412 + .flags = IORESOURCE_MEM, 413 + }, 414 + [1] = { 415 + .flags = IORESOURCE_MEM, 416 + .desc = IORES_DESC_PERSISTENT_MEMORY, 417 + }, 418 + }; 419 + struct platform_device *pdev; 420 + int i, rc; 421 + 422 + for (i = 0; i < ARRAY_SIZE(res); i++) { 423 + struct cxl_mock_res *r = alloc_mock_res(SZ_256M); 424 + 425 + if (!r) 426 + return NULL; 427 + res[i].start = r->range.start; 428 + res[i].end = r->range.end; 429 + } 430 + 431 + pdev = platform_device_alloc("cxl_mem", id); 432 + if (!pdev) 433 + return NULL; 434 + 435 + rc = platform_device_add_resources(pdev, res, ARRAY_SIZE(res)); 436 + if (rc) 437 + goto err; 438 + 439 + return pdev; 440 + 441 + err: 442 + platform_device_put(pdev); 443 + return NULL; 444 + } 445 + 414 446 static __init int cxl_test_init(void) 415 447 { 416 448 int rc, i; ··· 504 460 cxl_root_port[i] = pdev; 505 461 } 506 462 463 + BUILD_BUG_ON(ARRAY_SIZE(cxl_mem) != ARRAY_SIZE(cxl_root_port)); 464 + for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) { 465 + struct platform_device *port = cxl_root_port[i]; 466 + struct platform_device *pdev; 467 + 468 + pdev = alloc_memdev(i); 469 + if (!pdev) 470 + goto err_mem; 471 + pdev->dev.parent = &port->dev; 472 + 473 + rc = platform_device_add(pdev); 474 + if (rc) { 475 + platform_device_put(pdev); 476 + goto err_mem; 477 + } 478 + cxl_mem[i] = pdev; 479 + } 480 + 507 481 cxl_acpi = platform_device_alloc("cxl_acpi", 0); 508 482 if (!cxl_acpi) 509 - goto err_port; 483 + goto err_mem; 510 484 511 485 mock_companion(&acpi0017_mock, &cxl_acpi->dev); 512 486 acpi0017_mock.dev.bus = &platform_bus_type; ··· 537 475 538 476 err_add: 539 477 platform_device_put(cxl_acpi); 478 + err_mem: 479 + for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) 480 + platform_device_unregister(cxl_mem[i]); 540 481 err_port: 541 482 for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) 542 483 platform_device_unregister(cxl_root_port[i]); ··· 560 495 int i; 561 496 562 497 platform_device_unregister(cxl_acpi); 498 + for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) 499 + platform_device_unregister(cxl_mem[i]); 563 500 for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) 564 501 platform_device_unregister(cxl_root_port[i]); 565 502 for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--)
+256
tools/testing/cxl/test/mem.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + // Copyright(c) 2021 Intel Corporation. All rights reserved. 3 + 4 + #include <linux/platform_device.h> 5 + #include <linux/mod_devicetable.h> 6 + #include <linux/module.h> 7 + #include <linux/sizes.h> 8 + #include <linux/bits.h> 9 + #include <cxlmem.h> 10 + 11 + #define LSA_SIZE SZ_128K 12 + #define EFFECT(x) (1U << x) 13 + 14 + static struct cxl_cel_entry mock_cel[] = { 15 + { 16 + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_SUPPORTED_LOGS), 17 + .effect = cpu_to_le16(0), 18 + }, 19 + { 20 + .opcode = cpu_to_le16(CXL_MBOX_OP_IDENTIFY), 21 + .effect = cpu_to_le16(0), 22 + }, 23 + { 24 + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA), 25 + .effect = cpu_to_le16(0), 26 + }, 27 + { 28 + .opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA), 29 + .effect = cpu_to_le16(EFFECT(1) | EFFECT(2)), 30 + }, 31 + }; 32 + 33 + static struct { 34 + struct cxl_mbox_get_supported_logs gsl; 35 + struct cxl_gsl_entry entry; 36 + } mock_gsl_payload = { 37 + .gsl = { 38 + .entries = cpu_to_le16(1), 39 + }, 40 + .entry = { 41 + .uuid = DEFINE_CXL_CEL_UUID, 42 + .size = cpu_to_le32(sizeof(mock_cel)), 43 + }, 44 + }; 45 + 46 + static int mock_gsl(struct cxl_mbox_cmd *cmd) 47 + { 48 + if (cmd->size_out < sizeof(mock_gsl_payload)) 49 + return -EINVAL; 50 + 51 + memcpy(cmd->payload_out, &mock_gsl_payload, sizeof(mock_gsl_payload)); 52 + cmd->size_out = sizeof(mock_gsl_payload); 53 + 54 + return 0; 55 + } 56 + 57 + static int mock_get_log(struct cxl_mem *cxlm, struct cxl_mbox_cmd *cmd) 58 + { 59 + struct cxl_mbox_get_log *gl = cmd->payload_in; 60 + u32 offset = le32_to_cpu(gl->offset); 61 + u32 length = le32_to_cpu(gl->length); 62 + uuid_t uuid = DEFINE_CXL_CEL_UUID; 63 + void *data = &mock_cel; 64 + 65 + if (cmd->size_in < sizeof(*gl)) 66 + return -EINVAL; 67 + if (length > cxlm->payload_size) 68 + return -EINVAL; 69 + if (offset + length > sizeof(mock_cel)) 70 + return -EINVAL; 71 + if (!uuid_equal(&gl->uuid, &uuid)) 72 + return -EINVAL; 73 + if (length > cmd->size_out) 74 + return -EINVAL; 75 + 76 + memcpy(cmd->payload_out, data + offset, length); 77 + 78 + return 0; 79 + } 80 + 81 + static int mock_id(struct cxl_mem *cxlm, struct cxl_mbox_cmd *cmd) 82 + { 83 + struct platform_device *pdev = to_platform_device(cxlm->dev); 84 + struct cxl_mbox_identify id = { 85 + .fw_revision = { "mock fw v1 " }, 86 + .lsa_size = cpu_to_le32(LSA_SIZE), 87 + /* FIXME: Add partition support */ 88 + .partition_align = cpu_to_le64(0), 89 + }; 90 + u64 capacity = 0; 91 + int i; 92 + 93 + if (cmd->size_out < sizeof(id)) 94 + return -EINVAL; 95 + 96 + for (i = 0; i < 2; i++) { 97 + struct resource *res; 98 + 99 + res = platform_get_resource(pdev, IORESOURCE_MEM, i); 100 + if (!res) 101 + break; 102 + 103 + capacity += resource_size(res) / CXL_CAPACITY_MULTIPLIER; 104 + 105 + if (le64_to_cpu(id.partition_align)) 106 + continue; 107 + 108 + if (res->desc == IORES_DESC_PERSISTENT_MEMORY) 109 + id.persistent_capacity = cpu_to_le64( 110 + resource_size(res) / CXL_CAPACITY_MULTIPLIER); 111 + else 112 + id.volatile_capacity = cpu_to_le64( 113 + resource_size(res) / CXL_CAPACITY_MULTIPLIER); 114 + } 115 + 116 + id.total_capacity = cpu_to_le64(capacity); 117 + 118 + memcpy(cmd->payload_out, &id, sizeof(id)); 119 + 120 + return 0; 121 + } 122 + 123 + static int mock_get_lsa(struct cxl_mem *cxlm, struct cxl_mbox_cmd *cmd) 124 + { 125 + struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in; 126 + void *lsa = dev_get_drvdata(cxlm->dev); 127 + u32 offset, length; 128 + 129 + if (sizeof(*get_lsa) > cmd->size_in) 130 + return -EINVAL; 131 + offset = le32_to_cpu(get_lsa->offset); 132 + length = le32_to_cpu(get_lsa->length); 133 + if (offset + length > LSA_SIZE) 134 + return -EINVAL; 135 + if (length > cmd->size_out) 136 + return -EINVAL; 137 + 138 + memcpy(cmd->payload_out, lsa + offset, length); 139 + return 0; 140 + } 141 + 142 + static int mock_set_lsa(struct cxl_mem *cxlm, struct cxl_mbox_cmd *cmd) 143 + { 144 + struct cxl_mbox_set_lsa *set_lsa = cmd->payload_in; 145 + void *lsa = dev_get_drvdata(cxlm->dev); 146 + u32 offset, length; 147 + 148 + if (sizeof(*set_lsa) > cmd->size_in) 149 + return -EINVAL; 150 + offset = le32_to_cpu(set_lsa->offset); 151 + length = cmd->size_in - sizeof(*set_lsa); 152 + if (offset + length > LSA_SIZE) 153 + return -EINVAL; 154 + 155 + memcpy(lsa + offset, &set_lsa->data[0], length); 156 + return 0; 157 + } 158 + 159 + static int cxl_mock_mbox_send(struct cxl_mem *cxlm, struct cxl_mbox_cmd *cmd) 160 + { 161 + struct device *dev = cxlm->dev; 162 + int rc = -EIO; 163 + 164 + switch (cmd->opcode) { 165 + case CXL_MBOX_OP_GET_SUPPORTED_LOGS: 166 + rc = mock_gsl(cmd); 167 + break; 168 + case CXL_MBOX_OP_GET_LOG: 169 + rc = mock_get_log(cxlm, cmd); 170 + break; 171 + case CXL_MBOX_OP_IDENTIFY: 172 + rc = mock_id(cxlm, cmd); 173 + break; 174 + case CXL_MBOX_OP_GET_LSA: 175 + rc = mock_get_lsa(cxlm, cmd); 176 + break; 177 + case CXL_MBOX_OP_SET_LSA: 178 + rc = mock_set_lsa(cxlm, cmd); 179 + break; 180 + default: 181 + break; 182 + } 183 + 184 + dev_dbg(dev, "opcode: %#x sz_in: %zd sz_out: %zd rc: %d\n", cmd->opcode, 185 + cmd->size_in, cmd->size_out, rc); 186 + 187 + return rc; 188 + } 189 + 190 + static void label_area_release(void *lsa) 191 + { 192 + vfree(lsa); 193 + } 194 + 195 + static int cxl_mock_mem_probe(struct platform_device *pdev) 196 + { 197 + struct device *dev = &pdev->dev; 198 + struct cxl_memdev *cxlmd; 199 + struct cxl_mem *cxlm; 200 + void *lsa; 201 + int rc; 202 + 203 + lsa = vmalloc(LSA_SIZE); 204 + if (!lsa) 205 + return -ENOMEM; 206 + rc = devm_add_action_or_reset(dev, label_area_release, lsa); 207 + if (rc) 208 + return rc; 209 + dev_set_drvdata(dev, lsa); 210 + 211 + cxlm = cxl_mem_create(dev); 212 + if (IS_ERR(cxlm)) 213 + return PTR_ERR(cxlm); 214 + 215 + cxlm->mbox_send = cxl_mock_mbox_send; 216 + cxlm->payload_size = SZ_4K; 217 + 218 + rc = cxl_mem_enumerate_cmds(cxlm); 219 + if (rc) 220 + return rc; 221 + 222 + rc = cxl_mem_identify(cxlm); 223 + if (rc) 224 + return rc; 225 + 226 + rc = cxl_mem_create_range_info(cxlm); 227 + if (rc) 228 + return rc; 229 + 230 + cxlmd = devm_cxl_add_memdev(cxlm); 231 + if (IS_ERR(cxlmd)) 232 + return PTR_ERR(cxlmd); 233 + 234 + if (range_len(&cxlm->pmem_range) && IS_ENABLED(CONFIG_CXL_PMEM)) 235 + rc = devm_cxl_add_nvdimm(dev, cxlmd); 236 + 237 + return 0; 238 + } 239 + 240 + static const struct platform_device_id cxl_mock_mem_ids[] = { 241 + { .name = "cxl_mem", }, 242 + { }, 243 + }; 244 + MODULE_DEVICE_TABLE(platform, cxl_mock_mem_ids); 245 + 246 + static struct platform_driver cxl_mock_mem_driver = { 247 + .probe = cxl_mock_mem_probe, 248 + .id_table = cxl_mock_mem_ids, 249 + .driver = { 250 + .name = KBUILD_MODNAME, 251 + }, 252 + }; 253 + 254 + module_platform_driver(cxl_mock_mem_driver); 255 + MODULE_LICENSE("GPL v2"); 256 + MODULE_IMPORT_NS(CXL);