diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /tools/testing/cxl/test | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/testing/cxl/test')
-rw-r--r-- | tools/testing/cxl/test/Kbuild | 10 | ||||
-rw-r--r-- | tools/testing/cxl/test/cxl.c | 1457 | ||||
-rw-r--r-- | tools/testing/cxl/test/mem.c | 1556 | ||||
-rw-r--r-- | tools/testing/cxl/test/mock.c | 290 | ||||
-rw-r--r-- | tools/testing/cxl/test/mock.h | 33 |
5 files changed, 3346 insertions, 0 deletions
diff --git a/tools/testing/cxl/test/Kbuild b/tools/testing/cxl/test/Kbuild new file mode 100644 index 0000000000..61d5f7bcdd --- /dev/null +++ b/tools/testing/cxl/test/Kbuild @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-y := -I$(srctree)/drivers/cxl/ -I$(srctree)/drivers/cxl/core + +obj-m += cxl_test.o +obj-m += cxl_mock.o +obj-m += cxl_mock_mem.o + +cxl_test-y := cxl.o +cxl_mock-y := mock.o +cxl_mock_mem-y := mem.o diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c new file mode 100644 index 0000000000..f4e517a0c7 --- /dev/null +++ b/tools/testing/cxl/test/cxl.c @@ -0,0 +1,1457 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. All rights reserved. + +#include <linux/platform_device.h> +#include <linux/genalloc.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/mm.h> +#include <cxlmem.h> + +#include "../watermark.h" +#include "mock.h" + +static int interleave_arithmetic; + +#define NR_CXL_HOST_BRIDGES 2 +#define NR_CXL_SINGLE_HOST 1 +#define NR_CXL_RCH 1 +#define NR_CXL_ROOT_PORTS 2 +#define NR_CXL_SWITCH_PORTS 2 +#define NR_CXL_PORT_DECODERS 8 +#define NR_BRIDGES (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH) + +static struct platform_device *cxl_acpi; +static struct platform_device *cxl_host_bridge[NR_CXL_HOST_BRIDGES]; +#define NR_MULTI_ROOT (NR_CXL_HOST_BRIDGES * NR_CXL_ROOT_PORTS) +static struct platform_device *cxl_root_port[NR_MULTI_ROOT]; +static struct platform_device *cxl_switch_uport[NR_MULTI_ROOT]; +#define NR_MEM_MULTI \ + (NR_CXL_HOST_BRIDGES * NR_CXL_ROOT_PORTS * NR_CXL_SWITCH_PORTS) +static struct platform_device *cxl_switch_dport[NR_MEM_MULTI]; + +static struct platform_device *cxl_hb_single[NR_CXL_SINGLE_HOST]; +static struct platform_device *cxl_root_single[NR_CXL_SINGLE_HOST]; +static struct platform_device *cxl_swu_single[NR_CXL_SINGLE_HOST]; +#define NR_MEM_SINGLE (NR_CXL_SINGLE_HOST * NR_CXL_SWITCH_PORTS) +static struct platform_device *cxl_swd_single[NR_MEM_SINGLE]; + +struct platform_device *cxl_mem[NR_MEM_MULTI]; +struct platform_device *cxl_mem_single[NR_MEM_SINGLE]; + +static struct platform_device *cxl_rch[NR_CXL_RCH]; +static struct platform_device *cxl_rcd[NR_CXL_RCH]; + +static inline bool is_multi_bridge(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) + if (&cxl_host_bridge[i]->dev == dev) + return true; + return false; +} + +static inline bool is_single_bridge(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxl_hb_single); i++) + if (&cxl_hb_single[i]->dev == dev) + return true; + return false; +} + +static struct acpi_device acpi0017_mock; +static struct acpi_device host_bridge[NR_BRIDGES] = { + [0] = { + .handle = &host_bridge[0], + }, + [1] = { + .handle = &host_bridge[1], + }, + [2] = { + .handle = &host_bridge[2], + }, + [3] = { + .handle = &host_bridge[3], + }, +}; + +static bool is_mock_dev(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) + if (dev == &cxl_mem[i]->dev) + return true; + for (i = 0; i < ARRAY_SIZE(cxl_mem_single); i++) + if (dev == &cxl_mem_single[i]->dev) + return true; + for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++) + if (dev == &cxl_rcd[i]->dev) + return true; + if (dev == &cxl_acpi->dev) + return true; + return false; +} + +static bool is_mock_adev(struct acpi_device *adev) +{ + int i; + + if (adev == &acpi0017_mock) + return true; + + for (i = 0; i < ARRAY_SIZE(host_bridge); i++) + if (adev == &host_bridge[i]) + return true; + + return false; +} + +static struct { + struct acpi_table_cedt cedt; + struct acpi_cedt_chbs chbs[NR_BRIDGES]; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[1]; + } cfmws0; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[2]; + } cfmws1; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[1]; + } cfmws2; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[2]; + } cfmws3; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[1]; + } cfmws4; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[1]; + } cfmws5; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[1]; + } cfmws6; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[2]; + } cfmws7; + struct { + struct acpi_cedt_cfmws cfmws; + u32 target[4]; + } cfmws8; + struct { + struct acpi_cedt_cxims cxims; + u64 xormap_list[2]; + } cxims0; +} __packed mock_cedt = { + .cedt = { + .header = { + .signature = "CEDT", + .length = sizeof(mock_cedt), + .revision = 1, + }, + }, + .chbs[0] = { + .header = { + .type = ACPI_CEDT_TYPE_CHBS, + .length = sizeof(mock_cedt.chbs[0]), + }, + .uid = 0, + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20, + }, + .chbs[1] = { + .header = { + .type = ACPI_CEDT_TYPE_CHBS, + .length = sizeof(mock_cedt.chbs[0]), + }, + .uid = 1, + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20, + }, + .chbs[2] = { + .header = { + .type = ACPI_CEDT_TYPE_CHBS, + .length = sizeof(mock_cedt.chbs[0]), + }, + .uid = 2, + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20, + }, + .chbs[3] = { + .header = { + .type = ACPI_CEDT_TYPE_CHBS, + .length = sizeof(mock_cedt.chbs[0]), + }, + .uid = 3, + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL11, + }, + .cfmws0 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws0), + }, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, + .qtg_id = 0, + .window_size = SZ_256M * 4UL, + }, + .target = { 0 }, + }, + .cfmws1 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws1), + }, + .interleave_ways = 1, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, + .qtg_id = 1, + .window_size = SZ_256M * 8UL, + }, + .target = { 0, 1, }, + }, + .cfmws2 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws2), + }, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 2, + .window_size = SZ_256M * 4UL, + }, + .target = { 0 }, + }, + .cfmws3 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws3), + }, + .interleave_ways = 1, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 3, + .window_size = SZ_256M * 8UL, + }, + .target = { 0, 1, }, + }, + .cfmws4 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws4), + }, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 4, + .window_size = SZ_256M * 4UL, + }, + .target = { 2 }, + }, + .cfmws5 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws5), + }, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, + .qtg_id = 5, + .window_size = SZ_256M, + }, + .target = { 3 }, + }, + /* .cfmws6,7,8 use ACPI_CEDT_CFMWS_ARITHMETIC_XOR */ + .cfmws6 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws6), + }, + .interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 0, + .window_size = SZ_256M * 8UL, + }, + .target = { 0, }, + }, + .cfmws7 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws7), + }, + .interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR, + .interleave_ways = 1, + .granularity = 0, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 1, + .window_size = SZ_256M * 8UL, + }, + .target = { 0, 1, }, + }, + .cfmws8 = { + .cfmws = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws8), + }, + .interleave_arithmetic = ACPI_CEDT_CFMWS_ARITHMETIC_XOR, + .interleave_ways = 2, + .granularity = 0, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | + ACPI_CEDT_CFMWS_RESTRICT_PMEM, + .qtg_id = 0, + .window_size = SZ_256M * 16UL, + }, + .target = { 0, 1, 0, 1, }, + }, + .cxims0 = { + .cxims = { + .header = { + .type = ACPI_CEDT_TYPE_CXIMS, + .length = sizeof(mock_cedt.cxims0), + }, + .hbig = 0, + .nr_xormaps = 2, + }, + .xormap_list = { 0x404100, 0x808200, }, + }, +}; + +struct acpi_cedt_cfmws *mock_cfmws[] = { + [0] = &mock_cedt.cfmws0.cfmws, + [1] = &mock_cedt.cfmws1.cfmws, + [2] = &mock_cedt.cfmws2.cfmws, + [3] = &mock_cedt.cfmws3.cfmws, + [4] = &mock_cedt.cfmws4.cfmws, + [5] = &mock_cedt.cfmws5.cfmws, + /* Modulo Math above, XOR Math below */ + [6] = &mock_cedt.cfmws6.cfmws, + [7] = &mock_cedt.cfmws7.cfmws, + [8] = &mock_cedt.cfmws8.cfmws, +}; + +static int cfmws_start; +static int cfmws_end; +#define CFMWS_MOD_ARRAY_START 0 +#define CFMWS_MOD_ARRAY_END 5 +#define CFMWS_XOR_ARRAY_START 6 +#define CFMWS_XOR_ARRAY_END 8 + +struct acpi_cedt_cxims *mock_cxims[1] = { + [0] = &mock_cedt.cxims0.cxims, +}; + +struct cxl_mock_res { + struct list_head list; + struct range range; +}; + +static LIST_HEAD(mock_res); +static DEFINE_MUTEX(mock_res_lock); +static struct gen_pool *cxl_mock_pool; + +static void depopulate_all_mock_resources(void) +{ + struct cxl_mock_res *res, *_res; + + mutex_lock(&mock_res_lock); + list_for_each_entry_safe(res, _res, &mock_res, list) { + gen_pool_free(cxl_mock_pool, res->range.start, + range_len(&res->range)); + list_del(&res->list); + kfree(res); + } + mutex_unlock(&mock_res_lock); +} + +static struct cxl_mock_res *alloc_mock_res(resource_size_t size, int align) +{ + struct cxl_mock_res *res = kzalloc(sizeof(*res), GFP_KERNEL); + struct genpool_data_align data = { + .align = align, + }; + unsigned long phys; + + INIT_LIST_HEAD(&res->list); + phys = gen_pool_alloc_algo(cxl_mock_pool, size, + gen_pool_first_fit_align, &data); + if (!phys) + return NULL; + + res->range = (struct range) { + .start = phys, + .end = phys + size - 1, + }; + mutex_lock(&mock_res_lock); + list_add(&res->list, &mock_res); + mutex_unlock(&mock_res_lock); + + return res; +} + +static int populate_cedt(void) +{ + struct cxl_mock_res *res; + int i; + + for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) { + struct acpi_cedt_chbs *chbs = &mock_cedt.chbs[i]; + resource_size_t size; + + if (chbs->cxl_version == ACPI_CEDT_CHBS_VERSION_CXL20) + size = ACPI_CEDT_CHBS_LENGTH_CXL20; + else + size = ACPI_CEDT_CHBS_LENGTH_CXL11; + + res = alloc_mock_res(size, size); + if (!res) + return -ENOMEM; + chbs->base = res->range.start; + chbs->length = size; + } + + for (i = cfmws_start; i <= cfmws_end; i++) { + struct acpi_cedt_cfmws *window = mock_cfmws[i]; + + res = alloc_mock_res(window->window_size, SZ_256M); + if (!res) + return -ENOMEM; + window->base_hpa = res->range.start; + } + + return 0; +} + +static bool is_mock_port(struct device *dev); + +/* + * WARNING, this hack assumes the format of 'struct cxl_cfmws_context' + * and 'struct cxl_chbs_context' share the property that the first + * struct member is a cxl_test device being probed by the cxl_acpi + * driver. + */ +struct cxl_cedt_context { + struct device *dev; +}; + +static int mock_acpi_table_parse_cedt(enum acpi_cedt_type id, + acpi_tbl_entry_handler_arg handler_arg, + void *arg) +{ + struct cxl_cedt_context *ctx = arg; + struct device *dev = ctx->dev; + union acpi_subtable_headers *h; + unsigned long end; + int i; + + if (!is_mock_port(dev) && !is_mock_dev(dev)) + return acpi_table_parse_cedt(id, handler_arg, arg); + + if (id == ACPI_CEDT_TYPE_CHBS) + for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) { + h = (union acpi_subtable_headers *)&mock_cedt.chbs[i]; + end = (unsigned long)&mock_cedt.chbs[i + 1]; + handler_arg(h, arg, end); + } + + if (id == ACPI_CEDT_TYPE_CFMWS) + for (i = cfmws_start; i <= cfmws_end; i++) { + h = (union acpi_subtable_headers *) mock_cfmws[i]; + end = (unsigned long) h + mock_cfmws[i]->header.length; + handler_arg(h, arg, end); + } + + if (id == ACPI_CEDT_TYPE_CXIMS) + for (i = 0; i < ARRAY_SIZE(mock_cxims); i++) { + h = (union acpi_subtable_headers *)mock_cxims[i]; + end = (unsigned long)h + mock_cxims[i]->header.length; + handler_arg(h, arg, end); + } + + return 0; +} + +static bool is_mock_bridge(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) + if (dev == &cxl_host_bridge[i]->dev) + return true; + for (i = 0; i < ARRAY_SIZE(cxl_hb_single); i++) + if (dev == &cxl_hb_single[i]->dev) + return true; + for (i = 0; i < ARRAY_SIZE(cxl_rch); i++) + if (dev == &cxl_rch[i]->dev) + return true; + + return false; +} + +static bool is_mock_port(struct device *dev) +{ + int i; + + if (is_mock_bridge(dev)) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_root_port); i++) + if (dev == &cxl_root_port[i]->dev) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_switch_uport); i++) + if (dev == &cxl_switch_uport[i]->dev) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_switch_dport); i++) + if (dev == &cxl_switch_dport[i]->dev) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_root_single); i++) + if (dev == &cxl_root_single[i]->dev) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_swu_single); i++) + if (dev == &cxl_swu_single[i]->dev) + return true; + + for (i = 0; i < ARRAY_SIZE(cxl_swd_single); i++) + if (dev == &cxl_swd_single[i]->dev) + return true; + + if (is_cxl_memdev(dev)) + return is_mock_dev(dev->parent); + + return false; +} + +static int host_bridge_index(struct acpi_device *adev) +{ + return adev - host_bridge; +} + +static struct acpi_device *find_host_bridge(acpi_handle handle) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(host_bridge); i++) + if (handle == host_bridge[i].handle) + return &host_bridge[i]; + return NULL; +} + +static acpi_status +mock_acpi_evaluate_integer(acpi_handle handle, acpi_string pathname, + struct acpi_object_list *arguments, + unsigned long long *data) +{ + struct acpi_device *adev = find_host_bridge(handle); + + if (!adev || strcmp(pathname, METHOD_NAME__UID) != 0) + return acpi_evaluate_integer(handle, pathname, arguments, data); + + *data = host_bridge_index(adev); + return AE_OK; +} + +static struct pci_bus mock_pci_bus[NR_BRIDGES]; +static struct acpi_pci_root mock_pci_root[ARRAY_SIZE(mock_pci_bus)] = { + [0] = { + .bus = &mock_pci_bus[0], + }, + [1] = { + .bus = &mock_pci_bus[1], + }, + [2] = { + .bus = &mock_pci_bus[2], + }, + [3] = { + .bus = &mock_pci_bus[3], + }, + +}; + +static bool is_mock_bus(struct pci_bus *bus) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mock_pci_bus); i++) + if (bus == &mock_pci_bus[i]) + return true; + return false; +} + +static struct acpi_pci_root *mock_acpi_pci_find_root(acpi_handle handle) +{ + struct acpi_device *adev = find_host_bridge(handle); + + if (!adev) + return acpi_pci_find_root(handle); + return &mock_pci_root[host_bridge_index(adev)]; +} + +static struct cxl_hdm *mock_cxl_setup_hdm(struct cxl_port *port, + struct cxl_endpoint_dvsec_info *info) +{ + struct cxl_hdm *cxlhdm = devm_kzalloc(&port->dev, sizeof(*cxlhdm), GFP_KERNEL); + + if (!cxlhdm) + return ERR_PTR(-ENOMEM); + + cxlhdm->port = port; + return cxlhdm; +} + +static int mock_cxl_add_passthrough_decoder(struct cxl_port *port) +{ + dev_err(&port->dev, "unexpected passthrough decoder for cxl_test\n"); + return -EOPNOTSUPP; +} + + +struct target_map_ctx { + int *target_map; + int index; + int target_count; +}; + +static int map_targets(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct target_map_ctx *ctx = data; + + ctx->target_map[ctx->index++] = pdev->id; + + if (ctx->index > ctx->target_count) { + dev_WARN_ONCE(dev, 1, "too many targets found?\n"); + return -ENXIO; + } + + return 0; +} + +static int mock_decoder_commit(struct cxl_decoder *cxld) +{ + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + int id = cxld->id; + + if (cxld->flags & CXL_DECODER_F_ENABLE) + return 0; + + dev_dbg(&port->dev, "%s commit\n", dev_name(&cxld->dev)); + if (cxl_num_decoders_committed(port) != id) { + dev_dbg(&port->dev, + "%s: out of order commit, expected decoder%d.%d\n", + dev_name(&cxld->dev), port->id, + cxl_num_decoders_committed(port)); + return -EBUSY; + } + + port->commit_end++; + cxld->flags |= CXL_DECODER_F_ENABLE; + + return 0; +} + +static int mock_decoder_reset(struct cxl_decoder *cxld) +{ + struct cxl_port *port = to_cxl_port(cxld->dev.parent); + int id = cxld->id; + + if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0) + return 0; + + dev_dbg(&port->dev, "%s reset\n", dev_name(&cxld->dev)); + if (port->commit_end != id) { + dev_dbg(&port->dev, + "%s: out of order reset, expected decoder%d.%d\n", + dev_name(&cxld->dev), port->id, port->commit_end); + return -EBUSY; + } + + port->commit_end--; + cxld->flags &= ~CXL_DECODER_F_ENABLE; + + return 0; +} + +static void default_mock_decoder(struct cxl_decoder *cxld) +{ + cxld->hpa_range = (struct range){ + .start = 0, + .end = -1, + }; + + cxld->interleave_ways = 1; + cxld->interleave_granularity = 256; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; +} + +static int first_decoder(struct device *dev, void *data) +{ + struct cxl_decoder *cxld; + + if (!is_switch_decoder(dev)) + return 0; + cxld = to_cxl_decoder(dev); + if (cxld->id == 0) + return 1; + return 0; +} + +static void mock_init_hdm_decoder(struct cxl_decoder *cxld) +{ + struct acpi_cedt_cfmws *window = mock_cfmws[0]; + struct platform_device *pdev = NULL; + struct cxl_endpoint_decoder *cxled; + struct cxl_switch_decoder *cxlsd; + struct cxl_port *port, *iter; + const int size = SZ_512M; + struct cxl_memdev *cxlmd; + struct cxl_dport *dport; + struct device *dev; + bool hb0 = false; + u64 base; + int i; + + if (is_endpoint_decoder(&cxld->dev)) { + cxled = to_cxl_endpoint_decoder(&cxld->dev); + cxlmd = cxled_to_memdev(cxled); + WARN_ON(!dev_is_platform(cxlmd->dev.parent)); + pdev = to_platform_device(cxlmd->dev.parent); + + /* check is endpoint is attach to host-bridge0 */ + port = cxled_to_port(cxled); + do { + if (port->uport_dev == &cxl_host_bridge[0]->dev) { + hb0 = true; + break; + } + if (is_cxl_port(port->dev.parent)) + port = to_cxl_port(port->dev.parent); + else + port = NULL; + } while (port); + port = cxled_to_port(cxled); + } + + /* + * The first decoder on the first 2 devices on the first switch + * attached to host-bridge0 mock a fake / static RAM region. All + * other decoders are default disabled. Given the round robin + * assignment those devices are named cxl_mem.0, and cxl_mem.4. + * + * See 'cxl list -BMPu -m cxl_mem.0,cxl_mem.4' + */ + if (!hb0 || pdev->id % 4 || pdev->id > 4 || cxld->id > 0) { + default_mock_decoder(cxld); + return; + } + + base = window->base_hpa; + cxld->hpa_range = (struct range) { + .start = base, + .end = base + size - 1, + }; + + cxld->interleave_ways = 2; + eig_to_granularity(window->granularity, &cxld->interleave_granularity); + cxld->target_type = CXL_DECODER_HOSTONLYMEM; + cxld->flags = CXL_DECODER_F_ENABLE; + cxled->state = CXL_DECODER_STATE_AUTO; + port->commit_end = cxld->id; + devm_cxl_dpa_reserve(cxled, 0, size / cxld->interleave_ways, 0); + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; + + /* + * Now that endpoint decoder is set up, walk up the hierarchy + * and setup the switch and root port decoders targeting @cxlmd. + */ + iter = port; + for (i = 0; i < 2; i++) { + dport = iter->parent_dport; + iter = dport->port; + dev = device_find_child(&iter->dev, NULL, first_decoder); + /* + * Ancestor ports are guaranteed to be enumerated before + * @port, and all ports have at least one decoder. + */ + if (WARN_ON(!dev)) + continue; + cxlsd = to_cxl_switch_decoder(dev); + if (i == 0) { + /* put cxl_mem.4 second in the decode order */ + if (pdev->id == 4) + cxlsd->target[1] = dport; + else + cxlsd->target[0] = dport; + } else + cxlsd->target[0] = dport; + cxld = &cxlsd->cxld; + cxld->target_type = CXL_DECODER_HOSTONLYMEM; + cxld->flags = CXL_DECODER_F_ENABLE; + iter->commit_end = 0; + /* + * Switch targets 2 endpoints, while host bridge targets + * one root port + */ + if (i == 0) + cxld->interleave_ways = 2; + else + cxld->interleave_ways = 1; + cxld->interleave_granularity = 4096; + cxld->hpa_range = (struct range) { + .start = base, + .end = base + size - 1, + }; + put_device(dev); + } +} + +static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, + struct cxl_endpoint_dvsec_info *info) +{ + struct cxl_port *port = cxlhdm->port; + struct cxl_port *parent_port = to_cxl_port(port->dev.parent); + int target_count, i; + + if (is_cxl_endpoint(port)) + target_count = 0; + else if (is_cxl_root(parent_port)) + target_count = NR_CXL_ROOT_PORTS; + else + target_count = NR_CXL_SWITCH_PORTS; + + for (i = 0; i < NR_CXL_PORT_DECODERS; i++) { + int target_map[CXL_DECODER_MAX_INTERLEAVE] = { 0 }; + struct target_map_ctx ctx = { + .target_map = target_map, + .target_count = target_count, + }; + struct cxl_decoder *cxld; + int rc; + + if (target_count) { + struct cxl_switch_decoder *cxlsd; + + cxlsd = cxl_switch_decoder_alloc(port, target_count); + if (IS_ERR(cxlsd)) { + dev_warn(&port->dev, + "Failed to allocate the decoder\n"); + return PTR_ERR(cxlsd); + } + cxld = &cxlsd->cxld; + } else { + struct cxl_endpoint_decoder *cxled; + + cxled = cxl_endpoint_decoder_alloc(port); + + if (IS_ERR(cxled)) { + dev_warn(&port->dev, + "Failed to allocate the decoder\n"); + return PTR_ERR(cxled); + } + cxld = &cxled->cxld; + } + + mock_init_hdm_decoder(cxld); + + if (target_count) { + rc = device_for_each_child(port->uport_dev, &ctx, + map_targets); + if (rc) { + put_device(&cxld->dev); + return rc; + } + } + + rc = cxl_decoder_add_locked(cxld, target_map); + if (rc) { + put_device(&cxld->dev); + dev_err(&port->dev, "Failed to add decoder\n"); + return rc; + } + + rc = cxl_decoder_autoremove(&port->dev, cxld); + if (rc) + return rc; + dev_dbg(&cxld->dev, "Added to port %s\n", dev_name(&port->dev)); + } + + return 0; +} + +static int mock_cxl_port_enumerate_dports(struct cxl_port *port) +{ + struct platform_device **array; + int i, array_size; + + if (port->depth == 1) { + if (is_multi_bridge(port->uport_dev)) { + array_size = ARRAY_SIZE(cxl_root_port); + array = cxl_root_port; + } else if (is_single_bridge(port->uport_dev)) { + array_size = ARRAY_SIZE(cxl_root_single); + array = cxl_root_single; + } else { + dev_dbg(&port->dev, "%s: unknown bridge type\n", + dev_name(port->uport_dev)); + return -ENXIO; + } + } else if (port->depth == 2) { + struct cxl_port *parent = to_cxl_port(port->dev.parent); + + if (is_multi_bridge(parent->uport_dev)) { + array_size = ARRAY_SIZE(cxl_switch_dport); + array = cxl_switch_dport; + } else if (is_single_bridge(parent->uport_dev)) { + array_size = ARRAY_SIZE(cxl_swd_single); + array = cxl_swd_single; + } else { + dev_dbg(&port->dev, "%s: unknown bridge type\n", + dev_name(port->uport_dev)); + return -ENXIO; + } + } else { + dev_WARN_ONCE(&port->dev, 1, "unexpected depth %d\n", + port->depth); + return -ENXIO; + } + + for (i = 0; i < array_size; i++) { + struct platform_device *pdev = array[i]; + struct cxl_dport *dport; + + if (pdev->dev.parent != port->uport_dev) { + dev_dbg(&port->dev, "%s: mismatch parent %s\n", + dev_name(port->uport_dev), + dev_name(pdev->dev.parent)); + continue; + } + + dport = devm_cxl_add_dport(port, &pdev->dev, pdev->id, + CXL_RESOURCE_NONE); + + if (IS_ERR(dport)) + return PTR_ERR(dport); + } + + return 0; +} + +static struct cxl_mock_ops cxl_mock_ops = { + .is_mock_adev = is_mock_adev, + .is_mock_bridge = is_mock_bridge, + .is_mock_bus = is_mock_bus, + .is_mock_port = is_mock_port, + .is_mock_dev = is_mock_dev, + .acpi_table_parse_cedt = mock_acpi_table_parse_cedt, + .acpi_evaluate_integer = mock_acpi_evaluate_integer, + .acpi_pci_find_root = mock_acpi_pci_find_root, + .devm_cxl_port_enumerate_dports = mock_cxl_port_enumerate_dports, + .devm_cxl_setup_hdm = mock_cxl_setup_hdm, + .devm_cxl_add_passthrough_decoder = mock_cxl_add_passthrough_decoder, + .devm_cxl_enumerate_decoders = mock_cxl_enumerate_decoders, + .list = LIST_HEAD_INIT(cxl_mock_ops.list), +}; + +static void mock_companion(struct acpi_device *adev, struct device *dev) +{ + device_initialize(&adev->dev); + fwnode_init(&adev->fwnode, NULL); + dev->fwnode = &adev->fwnode; + adev->fwnode.dev = dev; +} + +#ifndef SZ_64G +#define SZ_64G (SZ_32G * 2) +#endif + +static __init int cxl_rch_init(void) +{ + int rc, i; + + for (i = 0; i < ARRAY_SIZE(cxl_rch); i++) { + int idx = NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + i; + struct acpi_device *adev = &host_bridge[idx]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_host_bridge", idx); + if (!pdev) + goto err_bridge; + + mock_companion(adev, &pdev->dev); + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_bridge; + } + + cxl_rch[i] = pdev; + mock_pci_bus[idx].bridge = &pdev->dev; + rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj, + "firmware_node"); + if (rc) + goto err_bridge; + } + + for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++) { + int idx = NR_MEM_MULTI + NR_MEM_SINGLE + i; + struct platform_device *rch = cxl_rch[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_rcd", idx); + if (!pdev) + goto err_mem; + pdev->dev.parent = &rch->dev; + set_dev_node(&pdev->dev, i % 2); + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_mem; + } + cxl_rcd[i] = pdev; + } + + return 0; + +err_mem: + for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--) + platform_device_unregister(cxl_rcd[i]); +err_bridge: + for (i = ARRAY_SIZE(cxl_rch) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_rch[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "firmware_node"); + platform_device_unregister(cxl_rch[i]); + } + + return rc; +} + +static void cxl_rch_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--) + platform_device_unregister(cxl_rcd[i]); + for (i = ARRAY_SIZE(cxl_rch) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_rch[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "firmware_node"); + platform_device_unregister(cxl_rch[i]); + } +} + +static __init int cxl_single_init(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(cxl_hb_single); i++) { + struct acpi_device *adev = + &host_bridge[NR_CXL_HOST_BRIDGES + i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_host_bridge", + NR_CXL_HOST_BRIDGES + i); + if (!pdev) + goto err_bridge; + + mock_companion(adev, &pdev->dev); + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_bridge; + } + + cxl_hb_single[i] = pdev; + mock_pci_bus[i + NR_CXL_HOST_BRIDGES].bridge = &pdev->dev; + rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj, + "physical_node"); + if (rc) + goto err_bridge; + } + + for (i = 0; i < ARRAY_SIZE(cxl_root_single); i++) { + struct platform_device *bridge = + cxl_hb_single[i % ARRAY_SIZE(cxl_hb_single)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_root_port", + NR_MULTI_ROOT + i); + if (!pdev) + goto err_port; + pdev->dev.parent = &bridge->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_port; + } + cxl_root_single[i] = pdev; + } + + for (i = 0; i < ARRAY_SIZE(cxl_swu_single); i++) { + struct platform_device *root_port = cxl_root_single[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_uport", + NR_MULTI_ROOT + i); + if (!pdev) + goto err_uport; + pdev->dev.parent = &root_port->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_uport; + } + cxl_swu_single[i] = pdev; + } + + for (i = 0; i < ARRAY_SIZE(cxl_swd_single); i++) { + struct platform_device *uport = + cxl_swu_single[i % ARRAY_SIZE(cxl_swu_single)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_dport", + i + NR_MEM_MULTI); + if (!pdev) + goto err_dport; + pdev->dev.parent = &uport->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_dport; + } + cxl_swd_single[i] = pdev; + } + + for (i = 0; i < ARRAY_SIZE(cxl_mem_single); i++) { + struct platform_device *dport = cxl_swd_single[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_mem", NR_MEM_MULTI + i); + if (!pdev) + goto err_mem; + pdev->dev.parent = &dport->dev; + set_dev_node(&pdev->dev, i % 2); + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_mem; + } + cxl_mem_single[i] = pdev; + } + + return 0; + +err_mem: + for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_mem_single[i]); +err_dport: + for (i = ARRAY_SIZE(cxl_swd_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_swd_single[i]); +err_uport: + for (i = ARRAY_SIZE(cxl_swu_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_swu_single[i]); +err_port: + for (i = ARRAY_SIZE(cxl_root_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_root_single[i]); +err_bridge: + for (i = ARRAY_SIZE(cxl_hb_single) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_hb_single[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "physical_node"); + platform_device_unregister(cxl_hb_single[i]); + } + + return rc; +} + +static void cxl_single_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_mem_single[i]); + for (i = ARRAY_SIZE(cxl_swd_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_swd_single[i]); + for (i = ARRAY_SIZE(cxl_swu_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_swu_single[i]); + for (i = ARRAY_SIZE(cxl_root_single) - 1; i >= 0; i--) + platform_device_unregister(cxl_root_single[i]); + for (i = ARRAY_SIZE(cxl_hb_single) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_hb_single[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "physical_node"); + platform_device_unregister(cxl_hb_single[i]); + } +} + +static __init int cxl_test_init(void) +{ + int rc, i; + + cxl_acpi_test(); + cxl_core_test(); + cxl_mem_test(); + cxl_pmem_test(); + cxl_port_test(); + + register_cxl_mock_ops(&cxl_mock_ops); + + cxl_mock_pool = gen_pool_create(ilog2(SZ_2M), NUMA_NO_NODE); + if (!cxl_mock_pool) { + rc = -ENOMEM; + goto err_gen_pool_create; + } + + rc = gen_pool_add(cxl_mock_pool, iomem_resource.end + 1 - SZ_64G, + SZ_64G, NUMA_NO_NODE); + if (rc) + goto err_gen_pool_add; + + if (interleave_arithmetic == 1) { + cfmws_start = CFMWS_XOR_ARRAY_START; + cfmws_end = CFMWS_XOR_ARRAY_END; + } else { + cfmws_start = CFMWS_MOD_ARRAY_START; + cfmws_end = CFMWS_MOD_ARRAY_END; + } + + rc = populate_cedt(); + if (rc) + goto err_populate; + + for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) { + struct acpi_device *adev = &host_bridge[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_host_bridge", i); + if (!pdev) + goto err_bridge; + + mock_companion(adev, &pdev->dev); + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_bridge; + } + + cxl_host_bridge[i] = pdev; + mock_pci_bus[i].bridge = &pdev->dev; + rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj, + "physical_node"); + if (rc) + goto err_bridge; + } + + for (i = 0; i < ARRAY_SIZE(cxl_root_port); i++) { + struct platform_device *bridge = + cxl_host_bridge[i % ARRAY_SIZE(cxl_host_bridge)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_root_port", i); + if (!pdev) + goto err_port; + pdev->dev.parent = &bridge->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_port; + } + cxl_root_port[i] = pdev; + } + + BUILD_BUG_ON(ARRAY_SIZE(cxl_switch_uport) != ARRAY_SIZE(cxl_root_port)); + for (i = 0; i < ARRAY_SIZE(cxl_switch_uport); i++) { + struct platform_device *root_port = cxl_root_port[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_uport", i); + if (!pdev) + goto err_uport; + pdev->dev.parent = &root_port->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_uport; + } + cxl_switch_uport[i] = pdev; + } + + for (i = 0; i < ARRAY_SIZE(cxl_switch_dport); i++) { + struct platform_device *uport = + cxl_switch_uport[i % ARRAY_SIZE(cxl_switch_uport)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_dport", i); + if (!pdev) + goto err_dport; + pdev->dev.parent = &uport->dev; + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_dport; + } + cxl_switch_dport[i] = pdev; + } + + for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) { + struct platform_device *dport = cxl_switch_dport[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_mem", i); + if (!pdev) + goto err_mem; + pdev->dev.parent = &dport->dev; + set_dev_node(&pdev->dev, i % 2); + + rc = platform_device_add(pdev); + if (rc) { + platform_device_put(pdev); + goto err_mem; + } + cxl_mem[i] = pdev; + } + + rc = cxl_single_init(); + if (rc) + goto err_mem; + + rc = cxl_rch_init(); + if (rc) + goto err_single; + + cxl_acpi = platform_device_alloc("cxl_acpi", 0); + if (!cxl_acpi) + goto err_rch; + + mock_companion(&acpi0017_mock, &cxl_acpi->dev); + acpi0017_mock.dev.bus = &platform_bus_type; + + rc = platform_device_add(cxl_acpi); + if (rc) + goto err_add; + + return 0; + +err_add: + platform_device_put(cxl_acpi); +err_rch: + cxl_rch_exit(); +err_single: + cxl_single_exit(); +err_mem: + for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) + platform_device_unregister(cxl_mem[i]); +err_dport: + for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--) + platform_device_unregister(cxl_switch_dport[i]); +err_uport: + for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--) + platform_device_unregister(cxl_switch_uport[i]); +err_port: + for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) + platform_device_unregister(cxl_root_port[i]); +err_bridge: + for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_host_bridge[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "physical_node"); + platform_device_unregister(cxl_host_bridge[i]); + } +err_populate: + depopulate_all_mock_resources(); +err_gen_pool_add: + gen_pool_destroy(cxl_mock_pool); +err_gen_pool_create: + unregister_cxl_mock_ops(&cxl_mock_ops); + return rc; +} + +static __exit void cxl_test_exit(void) +{ + int i; + + platform_device_unregister(cxl_acpi); + cxl_rch_exit(); + cxl_single_exit(); + for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) + platform_device_unregister(cxl_mem[i]); + for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--) + platform_device_unregister(cxl_switch_dport[i]); + for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--) + platform_device_unregister(cxl_switch_uport[i]); + for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) + platform_device_unregister(cxl_root_port[i]); + for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_host_bridge[i]; + + if (!pdev) + continue; + sysfs_remove_link(&pdev->dev.kobj, "physical_node"); + platform_device_unregister(cxl_host_bridge[i]); + } + depopulate_all_mock_resources(); + gen_pool_destroy(cxl_mock_pool); + unregister_cxl_mock_ops(&cxl_mock_ops); +} + +module_param(interleave_arithmetic, int, 0444); +MODULE_PARM_DESC(interleave_arithmetic, "Modulo:0, XOR:1"); +module_init(cxl_test_init); +module_exit(cxl_test_exit); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(ACPI); +MODULE_IMPORT_NS(CXL); diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c new file mode 100644 index 0000000000..68118c37f0 --- /dev/null +++ b/tools/testing/cxl/test/mem.c @@ -0,0 +1,1556 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. All rights reserved. + +#include <linux/platform_device.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/sizes.h> +#include <linux/bits.h> +#include <asm/unaligned.h> +#include <crypto/sha2.h> +#include <cxlmem.h> + +#include "trace.h" + +#define LSA_SIZE SZ_128K +#define FW_SIZE SZ_64M +#define FW_SLOTS 3 +#define DEV_SIZE SZ_2G +#define EFFECT(x) (1U << x) + +#define MOCK_INJECT_DEV_MAX 8 +#define MOCK_INJECT_TEST_MAX 128 + +static unsigned int poison_inject_dev_max = MOCK_INJECT_DEV_MAX; + +enum cxl_command_effects { + CONF_CHANGE_COLD_RESET = 0, + CONF_CHANGE_IMMEDIATE, + DATA_CHANGE_IMMEDIATE, + POLICY_CHANGE_IMMEDIATE, + LOG_CHANGE_IMMEDIATE, + SECURITY_CHANGE_IMMEDIATE, + BACKGROUND_OP, + SECONDARY_MBOX_SUPPORTED, +}; + +#define CXL_CMD_EFFECT_NONE cpu_to_le16(0) + +static struct cxl_cel_entry mock_cel[] = { + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_SUPPORTED_LOGS), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_IDENTIFY), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_PARTITION_INFO), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_IMMEDIATE) | + EFFECT(DATA_CHANGE_IMMEDIATE)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_HEALTH_INFO), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_POISON), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_INJECT_POISON), + .effect = cpu_to_le16(EFFECT(DATA_CHANGE_IMMEDIATE)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_CLEAR_POISON), + .effect = cpu_to_le16(EFFECT(DATA_CHANGE_IMMEDIATE)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_GET_FW_INFO), + .effect = CXL_CMD_EFFECT_NONE, + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_TRANSFER_FW), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_COLD_RESET) | + EFFECT(BACKGROUND_OP)), + }, + { + .opcode = cpu_to_le16(CXL_MBOX_OP_ACTIVATE_FW), + .effect = cpu_to_le16(EFFECT(CONF_CHANGE_COLD_RESET) | + EFFECT(CONF_CHANGE_IMMEDIATE)), + }, +}; + +/* See CXL 2.0 Table 181 Get Health Info Output Payload */ +struct cxl_mbox_health_info { + u8 health_status; + u8 media_status; + u8 ext_status; + u8 life_used; + __le16 temperature; + __le32 dirty_shutdowns; + __le32 volatile_errors; + __le32 pmem_errors; +} __packed; + +static struct { + struct cxl_mbox_get_supported_logs gsl; + struct cxl_gsl_entry entry; +} mock_gsl_payload = { + .gsl = { + .entries = cpu_to_le16(1), + }, + .entry = { + .uuid = DEFINE_CXL_CEL_UUID, + .size = cpu_to_le32(sizeof(mock_cel)), + }, +}; + +#define PASS_TRY_LIMIT 3 + +#define CXL_TEST_EVENT_CNT_MAX 15 + +/* Set a number of events to return at a time for simulation. */ +#define CXL_TEST_EVENT_CNT 3 + +struct mock_event_log { + u16 clear_idx; + u16 cur_idx; + u16 nr_events; + u16 nr_overflow; + u16 overflow_reset; + struct cxl_event_record_raw *events[CXL_TEST_EVENT_CNT_MAX]; +}; + +struct mock_event_store { + struct cxl_memdev_state *mds; + struct mock_event_log mock_logs[CXL_EVENT_TYPE_MAX]; + u32 ev_status; +}; + +struct cxl_mockmem_data { + void *lsa; + void *fw; + int fw_slot; + int fw_staged; + size_t fw_size; + u32 security_state; + u8 user_pass[NVDIMM_PASSPHRASE_LEN]; + u8 master_pass[NVDIMM_PASSPHRASE_LEN]; + int user_limit; + int master_limit; + struct mock_event_store mes; + u8 event_buf[SZ_4K]; + u64 timestamp; +}; + +static struct mock_event_log *event_find_log(struct device *dev, int log_type) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + + if (log_type >= CXL_EVENT_TYPE_MAX) + return NULL; + return &mdata->mes.mock_logs[log_type]; +} + +static struct cxl_event_record_raw *event_get_current(struct mock_event_log *log) +{ + return log->events[log->cur_idx]; +} + +static void event_reset_log(struct mock_event_log *log) +{ + log->cur_idx = 0; + log->clear_idx = 0; + log->nr_overflow = log->overflow_reset; +} + +/* Handle can never be 0 use 1 based indexing for handle */ +static u16 event_get_clear_handle(struct mock_event_log *log) +{ + return log->clear_idx + 1; +} + +/* Handle can never be 0 use 1 based indexing for handle */ +static __le16 event_get_cur_event_handle(struct mock_event_log *log) +{ + u16 cur_handle = log->cur_idx + 1; + + return cpu_to_le16(cur_handle); +} + +static bool event_log_empty(struct mock_event_log *log) +{ + return log->cur_idx == log->nr_events; +} + +static void mes_add_event(struct mock_event_store *mes, + enum cxl_event_log_type log_type, + struct cxl_event_record_raw *event) +{ + struct mock_event_log *log; + + if (WARN_ON(log_type >= CXL_EVENT_TYPE_MAX)) + return; + + log = &mes->mock_logs[log_type]; + + if ((log->nr_events + 1) > CXL_TEST_EVENT_CNT_MAX) { + log->nr_overflow++; + log->overflow_reset = log->nr_overflow; + return; + } + + log->events[log->nr_events] = event; + log->nr_events++; +} + +static int mock_get_event(struct device *dev, struct cxl_mbox_cmd *cmd) +{ + struct cxl_get_event_payload *pl; + struct mock_event_log *log; + u16 nr_overflow; + u8 log_type; + int i; + + if (cmd->size_in != sizeof(log_type)) + return -EINVAL; + + if (cmd->size_out < struct_size(pl, records, CXL_TEST_EVENT_CNT)) + return -EINVAL; + + log_type = *((u8 *)cmd->payload_in); + if (log_type >= CXL_EVENT_TYPE_MAX) + return -EINVAL; + + memset(cmd->payload_out, 0, cmd->size_out); + + log = event_find_log(dev, log_type); + if (!log || event_log_empty(log)) + return 0; + + pl = cmd->payload_out; + + for (i = 0; i < CXL_TEST_EVENT_CNT && !event_log_empty(log); i++) { + memcpy(&pl->records[i], event_get_current(log), + sizeof(pl->records[i])); + pl->records[i].hdr.handle = event_get_cur_event_handle(log); + log->cur_idx++; + } + + pl->record_count = cpu_to_le16(i); + if (!event_log_empty(log)) + pl->flags |= CXL_GET_EVENT_FLAG_MORE_RECORDS; + + if (log->nr_overflow) { + u64 ns; + + pl->flags |= CXL_GET_EVENT_FLAG_OVERFLOW; + pl->overflow_err_count = cpu_to_le16(nr_overflow); + ns = ktime_get_real_ns(); + ns -= 5000000000; /* 5s ago */ + pl->first_overflow_timestamp = cpu_to_le64(ns); + ns = ktime_get_real_ns(); + ns -= 1000000000; /* 1s ago */ + pl->last_overflow_timestamp = cpu_to_le64(ns); + } + + return 0; +} + +static int mock_clear_event(struct device *dev, struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_clear_event_payload *pl = cmd->payload_in; + struct mock_event_log *log; + u8 log_type = pl->event_log; + u16 handle; + int nr; + + if (log_type >= CXL_EVENT_TYPE_MAX) + return -EINVAL; + + log = event_find_log(dev, log_type); + if (!log) + return 0; /* No mock data in this log */ + + /* + * This check is technically not invalid per the specification AFAICS. + * (The host could 'guess' handles and clear them in order). + * However, this is not good behavior for the host so test it. + */ + if (log->clear_idx + pl->nr_recs > log->cur_idx) { + dev_err(dev, + "Attempting to clear more events than returned!\n"); + return -EINVAL; + } + + /* Check handle order prior to clearing events */ + for (nr = 0, handle = event_get_clear_handle(log); + nr < pl->nr_recs; + nr++, handle++) { + if (handle != le16_to_cpu(pl->handles[nr])) { + dev_err(dev, "Clearing events out of order\n"); + return -EINVAL; + } + } + + if (log->nr_overflow) + log->nr_overflow = 0; + + /* Clear events */ + log->clear_idx += pl->nr_recs; + return 0; +} + +static void cxl_mock_event_trigger(struct device *dev) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + struct mock_event_store *mes = &mdata->mes; + int i; + + for (i = CXL_EVENT_TYPE_INFO; i < CXL_EVENT_TYPE_MAX; i++) { + struct mock_event_log *log; + + log = event_find_log(dev, i); + if (log) + event_reset_log(log); + } + + cxl_mem_get_event_records(mes->mds, mes->ev_status); +} + +struct cxl_event_record_raw maint_needed = { + .hdr = { + .id = UUID_INIT(0xBA5EBA11, 0xABCD, 0xEFEB, + 0xa5, 0x5a, 0xa5, 0x5a, 0xa5, 0xa5, 0x5a, 0xa5), + .length = sizeof(struct cxl_event_record_raw), + .flags[0] = CXL_EVENT_RECORD_FLAG_MAINT_NEEDED, + /* .handle = Set dynamically */ + .related_handle = cpu_to_le16(0xa5b6), + }, + .data = { 0xDE, 0xAD, 0xBE, 0xEF }, +}; + +struct cxl_event_record_raw hardware_replace = { + .hdr = { + .id = UUID_INIT(0xABCDEFEB, 0xBA11, 0xBA5E, + 0xa5, 0x5a, 0xa5, 0x5a, 0xa5, 0xa5, 0x5a, 0xa5), + .length = sizeof(struct cxl_event_record_raw), + .flags[0] = CXL_EVENT_RECORD_FLAG_HW_REPLACE, + /* .handle = Set dynamically */ + .related_handle = cpu_to_le16(0xb6a5), + }, + .data = { 0xDE, 0xAD, 0xBE, 0xEF }, +}; + +struct cxl_event_gen_media gen_media = { + .hdr = { + .id = UUID_INIT(0xfbcd0a77, 0xc260, 0x417f, + 0x85, 0xa9, 0x08, 0x8b, 0x16, 0x21, 0xeb, 0xa6), + .length = sizeof(struct cxl_event_gen_media), + .flags[0] = CXL_EVENT_RECORD_FLAG_PERMANENT, + /* .handle = Set dynamically */ + .related_handle = cpu_to_le16(0), + }, + .phys_addr = cpu_to_le64(0x2000), + .descriptor = CXL_GMER_EVT_DESC_UNCORECTABLE_EVENT, + .type = CXL_GMER_MEM_EVT_TYPE_DATA_PATH_ERROR, + .transaction_type = CXL_GMER_TRANS_HOST_WRITE, + /* .validity_flags = <set below> */ + .channel = 1, + .rank = 30 +}; + +struct cxl_event_dram dram = { + .hdr = { + .id = UUID_INIT(0x601dcbb3, 0x9c06, 0x4eab, + 0xb8, 0xaf, 0x4e, 0x9b, 0xfb, 0x5c, 0x96, 0x24), + .length = sizeof(struct cxl_event_dram), + .flags[0] = CXL_EVENT_RECORD_FLAG_PERF_DEGRADED, + /* .handle = Set dynamically */ + .related_handle = cpu_to_le16(0), + }, + .phys_addr = cpu_to_le64(0x8000), + .descriptor = CXL_GMER_EVT_DESC_THRESHOLD_EVENT, + .type = CXL_GMER_MEM_EVT_TYPE_INV_ADDR, + .transaction_type = CXL_GMER_TRANS_INTERNAL_MEDIA_SCRUB, + /* .validity_flags = <set below> */ + .channel = 1, + .bank_group = 5, + .bank = 2, + .column = {0xDE, 0xAD}, +}; + +struct cxl_event_mem_module mem_module = { + .hdr = { + .id = UUID_INIT(0xfe927475, 0xdd59, 0x4339, + 0xa5, 0x86, 0x79, 0xba, 0xb1, 0x13, 0xb7, 0x74), + .length = sizeof(struct cxl_event_mem_module), + /* .handle = Set dynamically */ + .related_handle = cpu_to_le16(0), + }, + .event_type = CXL_MMER_TEMP_CHANGE, + .info = { + .health_status = CXL_DHI_HS_PERFORMANCE_DEGRADED, + .media_status = CXL_DHI_MS_ALL_DATA_LOST, + .add_status = (CXL_DHI_AS_CRITICAL << 2) | + (CXL_DHI_AS_WARNING << 4) | + (CXL_DHI_AS_WARNING << 5), + .device_temp = { 0xDE, 0xAD}, + .dirty_shutdown_cnt = { 0xde, 0xad, 0xbe, 0xef }, + .cor_vol_err_cnt = { 0xde, 0xad, 0xbe, 0xef }, + .cor_per_err_cnt = { 0xde, 0xad, 0xbe, 0xef }, + } +}; + +static int mock_set_timestamp(struct cxl_dev_state *cxlds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(cxlds->dev); + struct cxl_mbox_set_timestamp_in *ts = cmd->payload_in; + + if (cmd->size_in != sizeof(*ts)) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + mdata->timestamp = le64_to_cpu(ts->timestamp); + return 0; +} + +static void cxl_mock_add_event_logs(struct mock_event_store *mes) +{ + put_unaligned_le16(CXL_GMER_VALID_CHANNEL | CXL_GMER_VALID_RANK, + &gen_media.validity_flags); + + put_unaligned_le16(CXL_DER_VALID_CHANNEL | CXL_DER_VALID_BANK_GROUP | + CXL_DER_VALID_BANK | CXL_DER_VALID_COLUMN, + &dram.validity_flags); + + mes_add_event(mes, CXL_EVENT_TYPE_INFO, &maint_needed); + mes_add_event(mes, CXL_EVENT_TYPE_INFO, + (struct cxl_event_record_raw *)&gen_media); + mes_add_event(mes, CXL_EVENT_TYPE_INFO, + (struct cxl_event_record_raw *)&mem_module); + mes->ev_status |= CXLDEV_EVENT_STATUS_INFO; + + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &maint_needed); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, + (struct cxl_event_record_raw *)&dram); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, + (struct cxl_event_record_raw *)&gen_media); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, + (struct cxl_event_record_raw *)&mem_module); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, + (struct cxl_event_record_raw *)&dram); + /* Overflow this log */ + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FAIL, &hardware_replace); + mes->ev_status |= CXLDEV_EVENT_STATUS_FAIL; + + mes_add_event(mes, CXL_EVENT_TYPE_FATAL, &hardware_replace); + mes_add_event(mes, CXL_EVENT_TYPE_FATAL, + (struct cxl_event_record_raw *)&dram); + mes->ev_status |= CXLDEV_EVENT_STATUS_FATAL; +} + +static int mock_gsl(struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_out < sizeof(mock_gsl_payload)) + return -EINVAL; + + memcpy(cmd->payload_out, &mock_gsl_payload, sizeof(mock_gsl_payload)); + cmd->size_out = sizeof(mock_gsl_payload); + + return 0; +} + +static int mock_get_log(struct cxl_memdev_state *mds, struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_get_log *gl = cmd->payload_in; + u32 offset = le32_to_cpu(gl->offset); + u32 length = le32_to_cpu(gl->length); + uuid_t uuid = DEFINE_CXL_CEL_UUID; + void *data = &mock_cel; + + if (cmd->size_in < sizeof(*gl)) + return -EINVAL; + if (length > mds->payload_size) + return -EINVAL; + if (offset + length > sizeof(mock_cel)) + return -EINVAL; + if (!uuid_equal(&gl->uuid, &uuid)) + return -EINVAL; + if (length > cmd->size_out) + return -EINVAL; + + memcpy(cmd->payload_out, data + offset, length); + + return 0; +} + +static int mock_rcd_id(struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_identify id = { + .fw_revision = { "mock fw v1 " }, + .total_capacity = + cpu_to_le64(DEV_SIZE / CXL_CAPACITY_MULTIPLIER), + .volatile_capacity = + cpu_to_le64(DEV_SIZE / CXL_CAPACITY_MULTIPLIER), + }; + + if (cmd->size_out < sizeof(id)) + return -EINVAL; + + memcpy(cmd->payload_out, &id, sizeof(id)); + + return 0; +} + +static int mock_id(struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_identify id = { + .fw_revision = { "mock fw v1 " }, + .lsa_size = cpu_to_le32(LSA_SIZE), + .partition_align = + cpu_to_le64(SZ_256M / CXL_CAPACITY_MULTIPLIER), + .total_capacity = + cpu_to_le64(DEV_SIZE / CXL_CAPACITY_MULTIPLIER), + .inject_poison_limit = cpu_to_le16(MOCK_INJECT_TEST_MAX), + }; + + put_unaligned_le24(CXL_POISON_LIST_MAX, id.poison_list_max_mer); + + if (cmd->size_out < sizeof(id)) + return -EINVAL; + + memcpy(cmd->payload_out, &id, sizeof(id)); + + return 0; +} + +static int mock_partition_info(struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_get_partition_info pi = { + .active_volatile_cap = + cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER), + .active_persistent_cap = + cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER), + }; + + if (cmd->size_out < sizeof(pi)) + return -EINVAL; + + memcpy(cmd->payload_out, &pi, sizeof(pi)); + + return 0; +} + +static int mock_sanitize(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in != 0) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + if (mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + return 0; /* assume less than 2 secs, no bg */ +} + +static int mock_secure_erase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in != 0) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + return 0; +} + +static int mock_get_security_state(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in) + return -EINVAL; + + if (cmd->size_out != sizeof(u32)) + return -EINVAL; + + memcpy(cmd->payload_out, &mdata->security_state, sizeof(u32)); + + return 0; +} + +static void master_plimit_check(struct cxl_mockmem_data *mdata) +{ + if (mdata->master_limit == PASS_TRY_LIMIT) + return; + mdata->master_limit++; + if (mdata->master_limit == PASS_TRY_LIMIT) + mdata->security_state |= CXL_PMEM_SEC_STATE_MASTER_PLIMIT; +} + +static void user_plimit_check(struct cxl_mockmem_data *mdata) +{ + if (mdata->user_limit == PASS_TRY_LIMIT) + return; + mdata->user_limit++; + if (mdata->user_limit == PASS_TRY_LIMIT) + mdata->security_state |= CXL_PMEM_SEC_STATE_USER_PLIMIT; +} + +static int mock_set_passphrase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_set_pass *set_pass; + + if (cmd->size_in != sizeof(*set_pass)) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_FROZEN) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + set_pass = cmd->payload_in; + switch (set_pass->type) { + case CXL_PMEM_SEC_PASS_MASTER: + if (mdata->security_state & CXL_PMEM_SEC_STATE_MASTER_PLIMIT) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + /* + * CXL spec rev3.0 8.2.9.8.6.2, The master pasphrase shall only be set in + * the security disabled state when the user passphrase is not set. + */ + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + if (memcmp(mdata->master_pass, set_pass->old_pass, NVDIMM_PASSPHRASE_LEN)) { + master_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + memcpy(mdata->master_pass, set_pass->new_pass, NVDIMM_PASSPHRASE_LEN); + mdata->security_state |= CXL_PMEM_SEC_STATE_MASTER_PASS_SET; + return 0; + + case CXL_PMEM_SEC_PASS_USER: + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PLIMIT) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + if (memcmp(mdata->user_pass, set_pass->old_pass, NVDIMM_PASSPHRASE_LEN)) { + user_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + memcpy(mdata->user_pass, set_pass->new_pass, NVDIMM_PASSPHRASE_LEN); + mdata->security_state |= CXL_PMEM_SEC_STATE_USER_PASS_SET; + return 0; + + default: + cmd->return_code = CXL_MBOX_CMD_RC_INPUT; + } + return -EINVAL; +} + +static int mock_disable_passphrase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_disable_pass *dis_pass; + + if (cmd->size_in != sizeof(*dis_pass)) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_FROZEN) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + dis_pass = cmd->payload_in; + switch (dis_pass->type) { + case CXL_PMEM_SEC_PASS_MASTER: + if (mdata->security_state & CXL_PMEM_SEC_STATE_MASTER_PLIMIT) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (!(mdata->security_state & CXL_PMEM_SEC_STATE_MASTER_PASS_SET)) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (memcmp(dis_pass->pass, mdata->master_pass, NVDIMM_PASSPHRASE_LEN)) { + master_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + + mdata->master_limit = 0; + memset(mdata->master_pass, 0, NVDIMM_PASSPHRASE_LEN); + mdata->security_state &= ~CXL_PMEM_SEC_STATE_MASTER_PASS_SET; + return 0; + + case CXL_PMEM_SEC_PASS_USER: + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PLIMIT) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (!(mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET)) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (memcmp(dis_pass->pass, mdata->user_pass, NVDIMM_PASSPHRASE_LEN)) { + user_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + + mdata->user_limit = 0; + memset(mdata->user_pass, 0, NVDIMM_PASSPHRASE_LEN); + mdata->security_state &= ~(CXL_PMEM_SEC_STATE_USER_PASS_SET | + CXL_PMEM_SEC_STATE_LOCKED); + return 0; + + default: + cmd->return_code = CXL_MBOX_CMD_RC_INPUT; + return -EINVAL; + } + + return 0; +} + +static int mock_freeze_security(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in != 0) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_FROZEN) + return 0; + + mdata->security_state |= CXL_PMEM_SEC_STATE_FROZEN; + return 0; +} + +static int mock_unlock_security(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + if (cmd->size_in != NVDIMM_PASSPHRASE_LEN) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + if (mdata->security_state & CXL_PMEM_SEC_STATE_FROZEN) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (!(mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET)) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PLIMIT) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (!(mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED)) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (memcmp(cmd->payload_in, mdata->user_pass, NVDIMM_PASSPHRASE_LEN)) { + if (++mdata->user_limit == PASS_TRY_LIMIT) + mdata->security_state |= CXL_PMEM_SEC_STATE_USER_PLIMIT; + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + + mdata->user_limit = 0; + mdata->security_state &= ~CXL_PMEM_SEC_STATE_LOCKED; + return 0; +} + +static int mock_passphrase_secure_erase(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_pass_erase *erase; + + if (cmd->size_in != sizeof(*erase)) + return -EINVAL; + + if (cmd->size_out != 0) + return -EINVAL; + + erase = cmd->payload_in; + if (mdata->security_state & CXL_PMEM_SEC_STATE_FROZEN) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PLIMIT && + erase->type == CXL_PMEM_SEC_PASS_USER) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + if (mdata->security_state & CXL_PMEM_SEC_STATE_MASTER_PLIMIT && + erase->type == CXL_PMEM_SEC_PASS_MASTER) { + cmd->return_code = CXL_MBOX_CMD_RC_SECURITY; + return -ENXIO; + } + + switch (erase->type) { + case CXL_PMEM_SEC_PASS_MASTER: + /* + * The spec does not clearly define the behavior of the scenario + * where a master passphrase is passed in while the master + * passphrase is not set and user passphrase is not set. The + * code will take the assumption that it will behave the same + * as a CXL secure erase command without passphrase (0x4401). + */ + if (mdata->security_state & CXL_PMEM_SEC_STATE_MASTER_PASS_SET) { + if (memcmp(mdata->master_pass, erase->pass, + NVDIMM_PASSPHRASE_LEN)) { + master_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + mdata->master_limit = 0; + mdata->user_limit = 0; + mdata->security_state &= ~CXL_PMEM_SEC_STATE_USER_PASS_SET; + memset(mdata->user_pass, 0, NVDIMM_PASSPHRASE_LEN); + mdata->security_state &= ~CXL_PMEM_SEC_STATE_LOCKED; + } else { + /* + * CXL rev3 8.2.9.8.6.3 Disable Passphrase + * When master passphrase is disabled, the device shall + * return Invalid Input for the Passphrase Secure Erase + * command with master passphrase. + */ + return -EINVAL; + } + /* Scramble encryption keys so that data is effectively erased */ + break; + case CXL_PMEM_SEC_PASS_USER: + /* + * The spec does not clearly define the behavior of the scenario + * where a user passphrase is passed in while the user + * passphrase is not set. The code will take the assumption that + * it will behave the same as a CXL secure erase command without + * passphrase (0x4401). + */ + if (mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET) { + if (memcmp(mdata->user_pass, erase->pass, + NVDIMM_PASSPHRASE_LEN)) { + user_plimit_check(mdata); + cmd->return_code = CXL_MBOX_CMD_RC_PASSPHRASE; + return -ENXIO; + } + mdata->user_limit = 0; + mdata->security_state &= ~CXL_PMEM_SEC_STATE_USER_PASS_SET; + memset(mdata->user_pass, 0, NVDIMM_PASSPHRASE_LEN); + } + + /* + * CXL rev3 Table 8-118 + * If user passphrase is not set or supported by device, current + * passphrase value is ignored. Will make the assumption that + * the operation will proceed as secure erase w/o passphrase + * since spec is not explicit. + */ + + /* Scramble encryption keys so that data is effectively erased */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mock_get_lsa(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in; + void *lsa = mdata->lsa; + u32 offset, length; + + if (sizeof(*get_lsa) > cmd->size_in) + return -EINVAL; + offset = le32_to_cpu(get_lsa->offset); + length = le32_to_cpu(get_lsa->length); + if (offset + length > LSA_SIZE) + return -EINVAL; + if (length > cmd->size_out) + return -EINVAL; + + memcpy(cmd->payload_out, lsa + offset, length); + return 0; +} + +static int mock_set_lsa(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_set_lsa *set_lsa = cmd->payload_in; + void *lsa = mdata->lsa; + u32 offset, length; + + if (sizeof(*set_lsa) > cmd->size_in) + return -EINVAL; + offset = le32_to_cpu(set_lsa->offset); + length = cmd->size_in - sizeof(*set_lsa); + if (offset + length > LSA_SIZE) + return -EINVAL; + + memcpy(lsa + offset, &set_lsa->data[0], length); + return 0; +} + +static int mock_health_info(struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_health_info health_info = { + /* set flags for maint needed, perf degraded, hw replacement */ + .health_status = 0x7, + /* set media status to "All Data Lost" */ + .media_status = 0x3, + /* + * set ext_status flags for: + * ext_life_used: normal, + * ext_temperature: critical, + * ext_corrected_volatile: warning, + * ext_corrected_persistent: normal, + */ + .ext_status = 0x18, + .life_used = 15, + .temperature = cpu_to_le16(25), + .dirty_shutdowns = cpu_to_le32(10), + .volatile_errors = cpu_to_le32(20), + .pmem_errors = cpu_to_le32(30), + }; + + if (cmd->size_out < sizeof(health_info)) + return -EINVAL; + + memcpy(cmd->payload_out, &health_info, sizeof(health_info)); + return 0; +} + +static struct mock_poison { + struct cxl_dev_state *cxlds; + u64 dpa; +} mock_poison_list[MOCK_INJECT_TEST_MAX]; + +static struct cxl_mbox_poison_out * +cxl_get_injected_po(struct cxl_dev_state *cxlds, u64 offset, u64 length) +{ + struct cxl_mbox_poison_out *po; + int nr_records = 0; + u64 dpa; + + po = kzalloc(struct_size(po, record, poison_inject_dev_max), GFP_KERNEL); + if (!po) + return NULL; + + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (mock_poison_list[i].cxlds != cxlds) + continue; + if (mock_poison_list[i].dpa < offset || + mock_poison_list[i].dpa > offset + length - 1) + continue; + + dpa = mock_poison_list[i].dpa + CXL_POISON_SOURCE_INJECTED; + po->record[nr_records].address = cpu_to_le64(dpa); + po->record[nr_records].length = cpu_to_le32(1); + nr_records++; + if (nr_records == poison_inject_dev_max) + break; + } + + /* Always return count, even when zero */ + po->count = cpu_to_le16(nr_records); + + return po; +} + +static int mock_get_poison(struct cxl_dev_state *cxlds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_poison_in *pi = cmd->payload_in; + struct cxl_mbox_poison_out *po; + u64 offset = le64_to_cpu(pi->offset); + u64 length = le64_to_cpu(pi->length); + int nr_records; + + po = cxl_get_injected_po(cxlds, offset, length); + if (!po) + return -ENOMEM; + nr_records = le16_to_cpu(po->count); + memcpy(cmd->payload_out, po, struct_size(po, record, nr_records)); + cmd->size_out = struct_size(po, record, nr_records); + kfree(po); + + return 0; +} + +static bool mock_poison_dev_max_injected(struct cxl_dev_state *cxlds) +{ + int count = 0; + + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (mock_poison_list[i].cxlds == cxlds) + count++; + } + return (count >= poison_inject_dev_max); +} + +static bool mock_poison_add(struct cxl_dev_state *cxlds, u64 dpa) +{ + if (mock_poison_dev_max_injected(cxlds)) { + dev_dbg(cxlds->dev, + "Device poison injection limit has been reached: %d\n", + MOCK_INJECT_DEV_MAX); + return false; + } + + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (!mock_poison_list[i].cxlds) { + mock_poison_list[i].cxlds = cxlds; + mock_poison_list[i].dpa = dpa; + return true; + } + } + dev_dbg(cxlds->dev, + "Mock test poison injection limit has been reached: %d\n", + MOCK_INJECT_TEST_MAX); + + return false; +} + +static bool mock_poison_found(struct cxl_dev_state *cxlds, u64 dpa) +{ + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (mock_poison_list[i].cxlds == cxlds && + mock_poison_list[i].dpa == dpa) + return true; + } + return false; +} + +static int mock_inject_poison(struct cxl_dev_state *cxlds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_inject_poison *pi = cmd->payload_in; + u64 dpa = le64_to_cpu(pi->address); + + if (mock_poison_found(cxlds, dpa)) { + /* Not an error to inject poison if already poisoned */ + dev_dbg(cxlds->dev, "DPA: 0x%llx already poisoned\n", dpa); + return 0; + } + if (!mock_poison_add(cxlds, dpa)) + return -ENXIO; + + return 0; +} + +static bool mock_poison_del(struct cxl_dev_state *cxlds, u64 dpa) +{ + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (mock_poison_list[i].cxlds == cxlds && + mock_poison_list[i].dpa == dpa) { + mock_poison_list[i].cxlds = NULL; + return true; + } + } + return false; +} + +static int mock_clear_poison(struct cxl_dev_state *cxlds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_clear_poison *pi = cmd->payload_in; + u64 dpa = le64_to_cpu(pi->address); + + /* + * A real CXL device will write pi->write_data to the address + * being cleared. In this mock, just delete this address from + * the mock poison list. + */ + if (!mock_poison_del(cxlds, dpa)) + dev_dbg(cxlds->dev, "DPA: 0x%llx not in poison list\n", dpa); + + return 0; +} + +static bool mock_poison_list_empty(void) +{ + for (int i = 0; i < MOCK_INJECT_TEST_MAX; i++) { + if (mock_poison_list[i].cxlds) + return false; + } + return true; +} + +static ssize_t poison_inject_max_show(struct device_driver *drv, char *buf) +{ + return sysfs_emit(buf, "%u\n", poison_inject_dev_max); +} + +static ssize_t poison_inject_max_store(struct device_driver *drv, + const char *buf, size_t len) +{ + int val; + + if (kstrtoint(buf, 0, &val) < 0) + return -EINVAL; + + if (!mock_poison_list_empty()) + return -EBUSY; + + if (val <= MOCK_INJECT_TEST_MAX) + poison_inject_dev_max = val; + else + return -EINVAL; + + return len; +} + +static DRIVER_ATTR_RW(poison_inject_max); + +static struct attribute *cxl_mock_mem_core_attrs[] = { + &driver_attr_poison_inject_max.attr, + NULL +}; +ATTRIBUTE_GROUPS(cxl_mock_mem_core); + +static int mock_fw_info(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_get_fw_info fw_info = { + .num_slots = FW_SLOTS, + .slot_info = (mdata->fw_slot & 0x7) | + ((mdata->fw_staged & 0x7) << 3), + .activation_cap = 0, + }; + + strcpy(fw_info.slot_1_revision, "cxl_test_fw_001"); + strcpy(fw_info.slot_2_revision, "cxl_test_fw_002"); + strcpy(fw_info.slot_3_revision, "cxl_test_fw_003"); + strcpy(fw_info.slot_4_revision, ""); + + if (cmd->size_out < sizeof(fw_info)) + return -EINVAL; + + memcpy(cmd->payload_out, &fw_info, sizeof(fw_info)); + return 0; +} + +static int mock_transfer_fw(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_transfer_fw *transfer = cmd->payload_in; + void *fw = mdata->fw; + size_t offset, length; + + offset = le32_to_cpu(transfer->offset) * CXL_FW_TRANSFER_ALIGNMENT; + length = cmd->size_in - sizeof(*transfer); + if (offset + length > FW_SIZE) + return -EINVAL; + + switch (transfer->action) { + case CXL_FW_TRANSFER_ACTION_FULL: + if (offset != 0) + return -EINVAL; + fallthrough; + case CXL_FW_TRANSFER_ACTION_END: + if (transfer->slot == 0 || transfer->slot > FW_SLOTS) + return -EINVAL; + mdata->fw_size = offset + length; + break; + case CXL_FW_TRANSFER_ACTION_INITIATE: + case CXL_FW_TRANSFER_ACTION_CONTINUE: + break; + case CXL_FW_TRANSFER_ACTION_ABORT: + return 0; + default: + return -EINVAL; + } + + memcpy(fw + offset, transfer->data, length); + return 0; +} + +static int mock_activate_fw(struct cxl_mockmem_data *mdata, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_mbox_activate_fw *activate = cmd->payload_in; + + if (activate->slot == 0 || activate->slot > FW_SLOTS) + return -EINVAL; + + switch (activate->action) { + case CXL_FW_ACTIVATE_ONLINE: + mdata->fw_slot = activate->slot; + mdata->fw_staged = 0; + return 0; + case CXL_FW_ACTIVATE_OFFLINE: + mdata->fw_staged = activate->slot; + return 0; + } + + return -EINVAL; +} + +static int cxl_mock_mbox_send(struct cxl_memdev_state *mds, + struct cxl_mbox_cmd *cmd) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; + struct device *dev = cxlds->dev; + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + int rc = -EIO; + + switch (cmd->opcode) { + case CXL_MBOX_OP_SET_TIMESTAMP: + rc = mock_set_timestamp(cxlds, cmd); + break; + case CXL_MBOX_OP_GET_SUPPORTED_LOGS: + rc = mock_gsl(cmd); + break; + case CXL_MBOX_OP_GET_LOG: + rc = mock_get_log(mds, cmd); + break; + case CXL_MBOX_OP_IDENTIFY: + if (cxlds->rcd) + rc = mock_rcd_id(cmd); + else + rc = mock_id(cmd); + break; + case CXL_MBOX_OP_GET_LSA: + rc = mock_get_lsa(mdata, cmd); + break; + case CXL_MBOX_OP_GET_PARTITION_INFO: + rc = mock_partition_info(cmd); + break; + case CXL_MBOX_OP_GET_EVENT_RECORD: + rc = mock_get_event(dev, cmd); + break; + case CXL_MBOX_OP_CLEAR_EVENT_RECORD: + rc = mock_clear_event(dev, cmd); + break; + case CXL_MBOX_OP_SET_LSA: + rc = mock_set_lsa(mdata, cmd); + break; + case CXL_MBOX_OP_GET_HEALTH_INFO: + rc = mock_health_info(cmd); + break; + case CXL_MBOX_OP_SANITIZE: + rc = mock_sanitize(mdata, cmd); + break; + case CXL_MBOX_OP_SECURE_ERASE: + rc = mock_secure_erase(mdata, cmd); + break; + case CXL_MBOX_OP_GET_SECURITY_STATE: + rc = mock_get_security_state(mdata, cmd); + break; + case CXL_MBOX_OP_SET_PASSPHRASE: + rc = mock_set_passphrase(mdata, cmd); + break; + case CXL_MBOX_OP_DISABLE_PASSPHRASE: + rc = mock_disable_passphrase(mdata, cmd); + break; + case CXL_MBOX_OP_FREEZE_SECURITY: + rc = mock_freeze_security(mdata, cmd); + break; + case CXL_MBOX_OP_UNLOCK: + rc = mock_unlock_security(mdata, cmd); + break; + case CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE: + rc = mock_passphrase_secure_erase(mdata, cmd); + break; + case CXL_MBOX_OP_GET_POISON: + rc = mock_get_poison(cxlds, cmd); + break; + case CXL_MBOX_OP_INJECT_POISON: + rc = mock_inject_poison(cxlds, cmd); + break; + case CXL_MBOX_OP_CLEAR_POISON: + rc = mock_clear_poison(cxlds, cmd); + break; + case CXL_MBOX_OP_GET_FW_INFO: + rc = mock_fw_info(mdata, cmd); + break; + case CXL_MBOX_OP_TRANSFER_FW: + rc = mock_transfer_fw(mdata, cmd); + break; + case CXL_MBOX_OP_ACTIVATE_FW: + rc = mock_activate_fw(mdata, cmd); + break; + default: + break; + } + + dev_dbg(dev, "opcode: %#x sz_in: %zd sz_out: %zd rc: %d\n", cmd->opcode, + cmd->size_in, cmd->size_out, rc); + + return rc; +} + +static void label_area_release(void *lsa) +{ + vfree(lsa); +} + +static void fw_buf_release(void *buf) +{ + vfree(buf); +} + +static bool is_rcd(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + + return !!id->driver_data; +} + +static ssize_t event_trigger_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cxl_mock_event_trigger(dev); + return count; +} +static DEVICE_ATTR_WO(event_trigger); + +static int cxl_mock_mem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cxl_memdev *cxlmd; + struct cxl_memdev_state *mds; + struct cxl_dev_state *cxlds; + struct cxl_mockmem_data *mdata; + int rc; + + mdata = devm_kzalloc(dev, sizeof(*mdata), GFP_KERNEL); + if (!mdata) + return -ENOMEM; + dev_set_drvdata(dev, mdata); + + mdata->lsa = vmalloc(LSA_SIZE); + if (!mdata->lsa) + return -ENOMEM; + mdata->fw = vmalloc(FW_SIZE); + if (!mdata->fw) + return -ENOMEM; + mdata->fw_slot = 2; + + rc = devm_add_action_or_reset(dev, label_area_release, mdata->lsa); + if (rc) + return rc; + + rc = devm_add_action_or_reset(dev, fw_buf_release, mdata->fw); + if (rc) + return rc; + + mds = cxl_memdev_state_create(dev); + if (IS_ERR(mds)) + return PTR_ERR(mds); + + mds->mbox_send = cxl_mock_mbox_send; + mds->payload_size = SZ_4K; + mds->event.buf = (struct cxl_get_event_payload *) mdata->event_buf; + + cxlds = &mds->cxlds; + cxlds->serial = pdev->id; + if (is_rcd(pdev)) { + cxlds->rcd = true; + cxlds->component_reg_phys = CXL_RESOURCE_NONE; + } + + rc = cxl_enumerate_cmds(mds); + if (rc) + return rc; + + rc = cxl_poison_state_init(mds); + if (rc) + return rc; + + rc = cxl_set_timestamp(mds); + if (rc) + return rc; + + cxlds->media_ready = true; + rc = cxl_dev_state_identify(mds); + if (rc) + return rc; + + rc = cxl_mem_create_range_info(mds); + if (rc) + return rc; + + mdata->mes.mds = mds; + cxl_mock_add_event_logs(&mdata->mes); + + cxlmd = devm_cxl_add_memdev(&pdev->dev, cxlds); + if (IS_ERR(cxlmd)) + return PTR_ERR(cxlmd); + + rc = devm_cxl_setup_fw_upload(&pdev->dev, mds); + if (rc) + return rc; + + cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_ALL); + + return 0; +} + +static ssize_t security_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", + !!(mdata->security_state & CXL_PMEM_SEC_STATE_LOCKED)); +} + +static ssize_t security_lock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + u32 mask = CXL_PMEM_SEC_STATE_FROZEN | CXL_PMEM_SEC_STATE_USER_PLIMIT | + CXL_PMEM_SEC_STATE_MASTER_PLIMIT; + int val; + + if (kstrtoint(buf, 0, &val) < 0) + return -EINVAL; + + if (val == 1) { + if (!(mdata->security_state & CXL_PMEM_SEC_STATE_USER_PASS_SET)) + return -ENXIO; + mdata->security_state |= CXL_PMEM_SEC_STATE_LOCKED; + mdata->security_state &= ~mask; + } else { + return -EINVAL; + } + return count; +} + +static DEVICE_ATTR_RW(security_lock); + +static ssize_t fw_buf_checksum_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cxl_mockmem_data *mdata = dev_get_drvdata(dev); + u8 hash[SHA256_DIGEST_SIZE]; + unsigned char *hstr, *hptr; + struct sha256_state sctx; + ssize_t written = 0; + int i; + + sha256_init(&sctx); + sha256_update(&sctx, mdata->fw, mdata->fw_size); + sha256_final(&sctx, hash); + + hstr = kzalloc((SHA256_DIGEST_SIZE * 2) + 1, GFP_KERNEL); + if (!hstr) + return -ENOMEM; + + hptr = hstr; + for (i = 0; i < SHA256_DIGEST_SIZE; i++) + hptr += sprintf(hptr, "%02x", hash[i]); + + written = sysfs_emit(buf, "%s\n", hstr); + + kfree(hstr); + return written; +} + +static DEVICE_ATTR_RO(fw_buf_checksum); + +static struct attribute *cxl_mock_mem_attrs[] = { + &dev_attr_security_lock.attr, + &dev_attr_event_trigger.attr, + &dev_attr_fw_buf_checksum.attr, + NULL +}; +ATTRIBUTE_GROUPS(cxl_mock_mem); + +static const struct platform_device_id cxl_mock_mem_ids[] = { + { .name = "cxl_mem", 0 }, + { .name = "cxl_rcd", 1 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, cxl_mock_mem_ids); + +static struct platform_driver cxl_mock_mem_driver = { + .probe = cxl_mock_mem_probe, + .id_table = cxl_mock_mem_ids, + .driver = { + .name = KBUILD_MODNAME, + .dev_groups = cxl_mock_mem_groups, + .groups = cxl_mock_mem_core_groups, + }, +}; + +module_platform_driver(cxl_mock_mem_driver); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(CXL); diff --git a/tools/testing/cxl/test/mock.c b/tools/testing/cxl/test/mock.c new file mode 100644 index 0000000000..1a61e68e30 --- /dev/null +++ b/tools/testing/cxl/test/mock.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only +//Copyright(c) 2021 Intel Corporation. All rights reserved. + +#include <linux/libnvdimm.h> +#include <linux/rculist.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <cxlmem.h> +#include <cxlpci.h> +#include "mock.h" + +static LIST_HEAD(mock); + +void register_cxl_mock_ops(struct cxl_mock_ops *ops) +{ + list_add_rcu(&ops->list, &mock); +} +EXPORT_SYMBOL_GPL(register_cxl_mock_ops); + +DEFINE_STATIC_SRCU(cxl_mock_srcu); + +void unregister_cxl_mock_ops(struct cxl_mock_ops *ops) +{ + list_del_rcu(&ops->list); + synchronize_srcu(&cxl_mock_srcu); +} +EXPORT_SYMBOL_GPL(unregister_cxl_mock_ops); + +struct cxl_mock_ops *get_cxl_mock_ops(int *index) +{ + *index = srcu_read_lock(&cxl_mock_srcu); + return list_first_or_null_rcu(&mock, struct cxl_mock_ops, list); +} +EXPORT_SYMBOL_GPL(get_cxl_mock_ops); + +void put_cxl_mock_ops(int index) +{ + srcu_read_unlock(&cxl_mock_srcu, index); +} +EXPORT_SYMBOL_GPL(put_cxl_mock_ops); + +bool __wrap_is_acpi_device_node(const struct fwnode_handle *fwnode) +{ + struct acpi_device *adev = + container_of(fwnode, struct acpi_device, fwnode); + int index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + bool retval = false; + + if (ops) + retval = ops->is_mock_adev(adev); + + if (!retval) + retval = is_acpi_device_node(fwnode); + + put_cxl_mock_ops(index); + return retval; +} +EXPORT_SYMBOL(__wrap_is_acpi_device_node); + +int __wrap_acpi_table_parse_cedt(enum acpi_cedt_type id, + acpi_tbl_entry_handler_arg handler_arg, + void *arg) +{ + int index, rc; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops) + rc = ops->acpi_table_parse_cedt(id, handler_arg, arg); + else + rc = acpi_table_parse_cedt(id, handler_arg, arg); + + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_acpi_table_parse_cedt, ACPI); + +acpi_status __wrap_acpi_evaluate_integer(acpi_handle handle, + acpi_string pathname, + struct acpi_object_list *arguments, + unsigned long long *data) +{ + int index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + acpi_status status; + + if (ops) + status = ops->acpi_evaluate_integer(handle, pathname, arguments, + data); + else + status = acpi_evaluate_integer(handle, pathname, arguments, + data); + put_cxl_mock_ops(index); + + return status; +} +EXPORT_SYMBOL(__wrap_acpi_evaluate_integer); + +struct acpi_pci_root *__wrap_acpi_pci_find_root(acpi_handle handle) +{ + int index; + struct acpi_pci_root *root; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops) + root = ops->acpi_pci_find_root(handle); + else + root = acpi_pci_find_root(handle); + + put_cxl_mock_ops(index); + + return root; +} +EXPORT_SYMBOL_GPL(__wrap_acpi_pci_find_root); + +struct nvdimm_bus * +__wrap_nvdimm_bus_register(struct device *dev, + struct nvdimm_bus_descriptor *nd_desc) +{ + int index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_dev(dev->parent->parent)) + nd_desc->provider_name = "cxl_test"; + put_cxl_mock_ops(index); + + return nvdimm_bus_register(dev, nd_desc); +} +EXPORT_SYMBOL_GPL(__wrap_nvdimm_bus_register); + +struct cxl_hdm *__wrap_devm_cxl_setup_hdm(struct cxl_port *port, + struct cxl_endpoint_dvsec_info *info) + +{ + int index; + struct cxl_hdm *cxlhdm; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(port->uport_dev)) + cxlhdm = ops->devm_cxl_setup_hdm(port, info); + else + cxlhdm = devm_cxl_setup_hdm(port, info); + put_cxl_mock_ops(index); + + return cxlhdm; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_setup_hdm, CXL); + +int __wrap_devm_cxl_add_passthrough_decoder(struct cxl_port *port) +{ + int rc, index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(port->uport_dev)) + rc = ops->devm_cxl_add_passthrough_decoder(port); + else + rc = devm_cxl_add_passthrough_decoder(port); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_add_passthrough_decoder, CXL); + +int __wrap_devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, + struct cxl_endpoint_dvsec_info *info) +{ + int rc, index; + struct cxl_port *port = cxlhdm->port; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(port->uport_dev)) + rc = ops->devm_cxl_enumerate_decoders(cxlhdm, info); + else + rc = devm_cxl_enumerate_decoders(cxlhdm, info); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_enumerate_decoders, CXL); + +int __wrap_devm_cxl_port_enumerate_dports(struct cxl_port *port) +{ + int rc, index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(port->uport_dev)) + rc = ops->devm_cxl_port_enumerate_dports(port); + else + rc = devm_cxl_port_enumerate_dports(port); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_port_enumerate_dports, CXL); + +int __wrap_cxl_await_media_ready(struct cxl_dev_state *cxlds) +{ + int rc, index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_dev(cxlds->dev)) + rc = 0; + else + rc = cxl_await_media_ready(cxlds); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_cxl_await_media_ready, CXL); + +int __wrap_cxl_hdm_decode_init(struct cxl_dev_state *cxlds, + struct cxl_hdm *cxlhdm, + struct cxl_endpoint_dvsec_info *info) +{ + int rc = 0, index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_dev(cxlds->dev)) + rc = 0; + else + rc = cxl_hdm_decode_init(cxlds, cxlhdm, info); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_cxl_hdm_decode_init, CXL); + +int __wrap_cxl_dvsec_rr_decode(struct device *dev, int dvsec, + struct cxl_endpoint_dvsec_info *info) +{ + int rc = 0, index; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_dev(dev)) + rc = 0; + else + rc = cxl_dvsec_rr_decode(dev, dvsec, info); + put_cxl_mock_ops(index); + + return rc; +} +EXPORT_SYMBOL_NS_GPL(__wrap_cxl_dvsec_rr_decode, CXL); + +struct cxl_dport *__wrap_devm_cxl_add_rch_dport(struct cxl_port *port, + struct device *dport_dev, + int port_id, + resource_size_t rcrb) +{ + int index; + struct cxl_dport *dport; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(dport_dev)) { + dport = devm_cxl_add_dport(port, dport_dev, port_id, + CXL_RESOURCE_NONE); + if (!IS_ERR(dport)) { + dport->rcrb.base = rcrb; + dport->rch = true; + } + } else + dport = devm_cxl_add_rch_dport(port, dport_dev, port_id, rcrb); + put_cxl_mock_ops(index); + + return dport; +} +EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_add_rch_dport, CXL); + +resource_size_t __wrap_cxl_rcd_component_reg_phys(struct device *dev, + struct cxl_dport *dport) +{ + int index; + resource_size_t component_reg_phys; + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); + + if (ops && ops->is_mock_port(dev)) + component_reg_phys = CXL_RESOURCE_NONE; + else + component_reg_phys = cxl_rcd_component_reg_phys(dev, dport); + put_cxl_mock_ops(index); + + return component_reg_phys; +} +EXPORT_SYMBOL_NS_GPL(__wrap_cxl_rcd_component_reg_phys, CXL); + +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(ACPI); +MODULE_IMPORT_NS(CXL); diff --git a/tools/testing/cxl/test/mock.h b/tools/testing/cxl/test/mock.h new file mode 100644 index 0000000000..a942237503 --- /dev/null +++ b/tools/testing/cxl/test/mock.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include <linux/list.h> +#include <linux/acpi.h> +#include <cxl.h> + +struct cxl_mock_ops { + struct list_head list; + bool (*is_mock_adev)(struct acpi_device *dev); + int (*acpi_table_parse_cedt)(enum acpi_cedt_type id, + acpi_tbl_entry_handler_arg handler_arg, + void *arg); + bool (*is_mock_bridge)(struct device *dev); + acpi_status (*acpi_evaluate_integer)(acpi_handle handle, + acpi_string pathname, + struct acpi_object_list *arguments, + unsigned long long *data); + struct acpi_pci_root *(*acpi_pci_find_root)(acpi_handle handle); + bool (*is_mock_bus)(struct pci_bus *bus); + bool (*is_mock_port)(struct device *dev); + bool (*is_mock_dev)(struct device *dev); + int (*devm_cxl_port_enumerate_dports)(struct cxl_port *port); + struct cxl_hdm *(*devm_cxl_setup_hdm)( + struct cxl_port *port, struct cxl_endpoint_dvsec_info *info); + int (*devm_cxl_add_passthrough_decoder)(struct cxl_port *port); + int (*devm_cxl_enumerate_decoders)( + struct cxl_hdm *hdm, struct cxl_endpoint_dvsec_info *info); +}; + +void register_cxl_mock_ops(struct cxl_mock_ops *ops); +void unregister_cxl_mock_ops(struct cxl_mock_ops *ops); +struct cxl_mock_ops *get_cxl_mock_ops(int *index); +void put_cxl_mock_ops(int index); |