xref: /openbmc/linux/drivers/cxl/pmem.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
18fdcb170SDan Williams // SPDX-License-Identifier: GPL-2.0-only
28fdcb170SDan Williams /* Copyright(c) 2021 Intel Corporation. All rights reserved. */
38fdcb170SDan Williams #include <linux/libnvdimm.h>
460b8f172SDan Williams #include <asm/unaligned.h>
58fdcb170SDan Williams #include <linux/device.h>
68fdcb170SDan Williams #include <linux/module.h>
721083f51SDan Williams #include <linux/ndctl.h>
821083f51SDan Williams #include <linux/async.h>
98fdcb170SDan Williams #include <linux/slab.h>
1004ad63f0SDan Williams #include <linux/nd.h>
115161a55cSBen Widawsky #include "cxlmem.h"
128fdcb170SDan Williams #include "cxl.h"
138fdcb170SDan Williams 
1432828115SDave Jiang extern const struct nvdimm_security_ops *cxl_security_ops;
1532828115SDave Jiang 
1612f3856aSDan Williams static __read_mostly DECLARE_BITMAP(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX);
1712f3856aSDan Williams 
clear_exclusive(void * mds)18*59f8d151SDan Williams static void clear_exclusive(void *mds)
1912f3856aSDan Williams {
20*59f8d151SDan Williams 	clear_exclusive_cxl_commands(mds, exclusive_cmds);
2112f3856aSDan Williams }
2212f3856aSDan Williams 
unregister_nvdimm(void * nvdimm)2321083f51SDan Williams static void unregister_nvdimm(void *nvdimm)
2421083f51SDan Williams {
2521083f51SDan Williams 	nvdimm_delete(nvdimm);
2621083f51SDan Williams }
2721083f51SDan Williams 
provider_show(struct device * dev,struct device_attribute * attr,char * buf)28452996faSDave Jiang static ssize_t provider_show(struct device *dev, struct device_attribute *attr, char *buf)
29452996faSDave Jiang {
30452996faSDave Jiang 	struct nvdimm *nvdimm = to_nvdimm(dev);
31452996faSDave Jiang 	struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
32452996faSDave Jiang 
33452996faSDave Jiang 	return sysfs_emit(buf, "%s\n", dev_name(&cxl_nvd->dev));
34452996faSDave Jiang }
35452996faSDave Jiang static DEVICE_ATTR_RO(provider);
36452996faSDave Jiang 
id_show(struct device * dev,struct device_attribute * attr,char * buf)37bd429e53SDave Jiang static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf)
38bd429e53SDave Jiang {
39bd429e53SDave Jiang 	struct nvdimm *nvdimm = to_nvdimm(dev);
40bd429e53SDave Jiang 	struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
41bd429e53SDave Jiang 	struct cxl_dev_state *cxlds = cxl_nvd->cxlmd->cxlds;
42bd429e53SDave Jiang 
43bd429e53SDave Jiang 	return sysfs_emit(buf, "%lld\n", cxlds->serial);
44bd429e53SDave Jiang }
45bd429e53SDave Jiang static DEVICE_ATTR_RO(id);
46bd429e53SDave Jiang 
47bd429e53SDave Jiang static struct attribute *cxl_dimm_attributes[] = {
48bd429e53SDave Jiang 	&dev_attr_id.attr,
49452996faSDave Jiang 	&dev_attr_provider.attr,
50bd429e53SDave Jiang 	NULL
51bd429e53SDave Jiang };
52bd429e53SDave Jiang 
53bd429e53SDave Jiang static const struct attribute_group cxl_dimm_attribute_group = {
54bd429e53SDave Jiang 	.name = "cxl",
55bd429e53SDave Jiang 	.attrs = cxl_dimm_attributes,
56bd429e53SDave Jiang };
57bd429e53SDave Jiang 
58bd429e53SDave Jiang static const struct attribute_group *cxl_dimm_attribute_groups[] = {
59bd429e53SDave Jiang 	&cxl_dimm_attribute_group,
60bd429e53SDave Jiang 	NULL
61bd429e53SDave Jiang };
62bd429e53SDave Jiang 
cxl_nvdimm_probe(struct device * dev)6321083f51SDan Williams static int cxl_nvdimm_probe(struct device *dev)
6421083f51SDan Williams {
6521083f51SDan Williams 	struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
6612f3856aSDan Williams 	struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
67f17b558dSDan Williams 	struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb;
68*59f8d151SDan Williams 	struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
6960b8f172SDan Williams 	unsigned long flags = 0, cmd_mask = 0;
7021083f51SDan Williams 	struct nvdimm *nvdimm;
7112f3856aSDan Williams 	int rc;
7221083f51SDan Williams 
73*59f8d151SDan Williams 	set_exclusive_cxl_commands(mds, exclusive_cmds);
74*59f8d151SDan Williams 	rc = devm_add_action_or_reset(dev, clear_exclusive, mds);
7512f3856aSDan Williams 	if (rc)
76f17b558dSDan Williams 		return rc;
7721083f51SDan Williams 
7821083f51SDan Williams 	set_bit(NDD_LABELING, &flags);
79f57aec44SDan Williams 	set_bit(NDD_REGISTER_SYNC, &flags);
8060b8f172SDan Williams 	set_bit(ND_CMD_GET_CONFIG_SIZE, &cmd_mask);
8160b8f172SDan Williams 	set_bit(ND_CMD_GET_CONFIG_DATA, &cmd_mask);
8260b8f172SDan Williams 	set_bit(ND_CMD_SET_CONFIG_DATA, &cmd_mask);
83bd429e53SDave Jiang 	nvdimm = __nvdimm_create(cxl_nvb->nvdimm_bus, cxl_nvd,
84bd429e53SDave Jiang 				 cxl_dimm_attribute_groups, flags,
85b5807c80SDave Jiang 				 cmd_mask, 0, NULL, cxl_nvd->dev_id,
86b5807c80SDave Jiang 				 cxl_security_ops, NULL);
87f17b558dSDan Williams 	if (!nvdimm)
88f17b558dSDan Williams 		return -ENOMEM;
8921083f51SDan Williams 
9012f3856aSDan Williams 	dev_set_drvdata(dev, nvdimm);
91f17b558dSDan Williams 	return devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
9221083f51SDan Williams }
9321083f51SDan Williams 
9421083f51SDan Williams static struct cxl_driver cxl_nvdimm_driver = {
9521083f51SDan Williams 	.name = "cxl_nvdimm",
9621083f51SDan Williams 	.probe = cxl_nvdimm_probe,
9721083f51SDan Williams 	.id = CXL_DEVICE_NVDIMM,
98cb9cfff8SDan Williams 	.drv = {
99cb9cfff8SDan Williams 		.suppress_bind_attrs = true,
100cb9cfff8SDan Williams 	},
10121083f51SDan Williams };
10221083f51SDan Williams 
cxl_pmem_get_config_size(struct cxl_memdev_state * mds,struct nd_cmd_get_config_size * cmd,unsigned int buf_len)103*59f8d151SDan Williams static int cxl_pmem_get_config_size(struct cxl_memdev_state *mds,
10460b8f172SDan Williams 				    struct nd_cmd_get_config_size *cmd,
10560b8f172SDan Williams 				    unsigned int buf_len)
10660b8f172SDan Williams {
10760b8f172SDan Williams 	if (sizeof(*cmd) > buf_len)
10860b8f172SDan Williams 		return -EINVAL;
10960b8f172SDan Williams 
11060b8f172SDan Williams 	*cmd = (struct nd_cmd_get_config_size){
111*59f8d151SDan Williams 		.config_size = mds->lsa_size,
112*59f8d151SDan Williams 		.max_xfer =
113*59f8d151SDan Williams 			mds->payload_size - sizeof(struct cxl_mbox_set_lsa),
11460b8f172SDan Williams 	};
11560b8f172SDan Williams 
11660b8f172SDan Williams 	return 0;
11760b8f172SDan Williams }
11860b8f172SDan Williams 
cxl_pmem_get_config_data(struct cxl_memdev_state * mds,struct nd_cmd_get_config_data_hdr * cmd,unsigned int buf_len)119*59f8d151SDan Williams static int cxl_pmem_get_config_data(struct cxl_memdev_state *mds,
12060b8f172SDan Williams 				    struct nd_cmd_get_config_data_hdr *cmd,
12160b8f172SDan Williams 				    unsigned int buf_len)
12260b8f172SDan Williams {
12349be6dd8SDan Williams 	struct cxl_mbox_get_lsa get_lsa;
1245331cdf4SDan Williams 	struct cxl_mbox_cmd mbox_cmd;
12560b8f172SDan Williams 	int rc;
12660b8f172SDan Williams 
12760b8f172SDan Williams 	if (sizeof(*cmd) > buf_len)
12860b8f172SDan Williams 		return -EINVAL;
12960b8f172SDan Williams 	if (struct_size(cmd, out_buf, cmd->in_length) > buf_len)
13060b8f172SDan Williams 		return -EINVAL;
13160b8f172SDan Williams 
13260b8f172SDan Williams 	get_lsa = (struct cxl_mbox_get_lsa) {
1338a664875SAlison Schofield 		.offset = cpu_to_le32(cmd->in_offset),
1348a664875SAlison Schofield 		.length = cpu_to_le32(cmd->in_length),
13560b8f172SDan Williams 	};
1365331cdf4SDan Williams 	mbox_cmd = (struct cxl_mbox_cmd) {
1375331cdf4SDan Williams 		.opcode = CXL_MBOX_OP_GET_LSA,
1385331cdf4SDan Williams 		.payload_in = &get_lsa,
1395331cdf4SDan Williams 		.size_in = sizeof(get_lsa),
1405331cdf4SDan Williams 		.size_out = cmd->in_length,
1415331cdf4SDan Williams 		.payload_out = cmd->out_buf,
1425331cdf4SDan Williams 	};
14360b8f172SDan Williams 
144*59f8d151SDan Williams 	rc = cxl_internal_send_cmd(mds, &mbox_cmd);
14560b8f172SDan Williams 	cmd->status = 0;
14660b8f172SDan Williams 
14760b8f172SDan Williams 	return rc;
14860b8f172SDan Williams }
14960b8f172SDan Williams 
cxl_pmem_set_config_data(struct cxl_memdev_state * mds,struct nd_cmd_set_config_hdr * cmd,unsigned int buf_len)150*59f8d151SDan Williams static int cxl_pmem_set_config_data(struct cxl_memdev_state *mds,
15160b8f172SDan Williams 				    struct nd_cmd_set_config_hdr *cmd,
15260b8f172SDan Williams 				    unsigned int buf_len)
15360b8f172SDan Williams {
15449be6dd8SDan Williams 	struct cxl_mbox_set_lsa *set_lsa;
1555331cdf4SDan Williams 	struct cxl_mbox_cmd mbox_cmd;
15660b8f172SDan Williams 	int rc;
15760b8f172SDan Williams 
15860b8f172SDan Williams 	if (sizeof(*cmd) > buf_len)
15960b8f172SDan Williams 		return -EINVAL;
16060b8f172SDan Williams 
16160b8f172SDan Williams 	/* 4-byte status follows the input data in the payload */
1624f1aa35fSYu Zhe 	if (size_add(struct_size(cmd, in_buf, cmd->in_length), 4) > buf_len)
16360b8f172SDan Williams 		return -EINVAL;
16460b8f172SDan Williams 
16560b8f172SDan Williams 	set_lsa =
16660b8f172SDan Williams 		kvzalloc(struct_size(set_lsa, data, cmd->in_length), GFP_KERNEL);
16760b8f172SDan Williams 	if (!set_lsa)
16860b8f172SDan Williams 		return -ENOMEM;
16960b8f172SDan Williams 
17060b8f172SDan Williams 	*set_lsa = (struct cxl_mbox_set_lsa) {
1718a664875SAlison Schofield 		.offset = cpu_to_le32(cmd->in_offset),
17260b8f172SDan Williams 	};
17360b8f172SDan Williams 	memcpy(set_lsa->data, cmd->in_buf, cmd->in_length);
1745331cdf4SDan Williams 	mbox_cmd = (struct cxl_mbox_cmd) {
1755331cdf4SDan Williams 		.opcode = CXL_MBOX_OP_SET_LSA,
1765331cdf4SDan Williams 		.payload_in = set_lsa,
1775331cdf4SDan Williams 		.size_in = struct_size(set_lsa, data, cmd->in_length),
1785331cdf4SDan Williams 	};
17960b8f172SDan Williams 
180*59f8d151SDan Williams 	rc = cxl_internal_send_cmd(mds, &mbox_cmd);
18160b8f172SDan Williams 
18260b8f172SDan Williams 	/*
18360b8f172SDan Williams 	 * Set "firmware" status (4-packed bytes at the end of the input
18460b8f172SDan Williams 	 * payload.
18560b8f172SDan Williams 	 */
18660b8f172SDan Williams 	put_unaligned(0, (u32 *) &cmd->in_buf[cmd->in_length]);
18760b8f172SDan Williams 	kvfree(set_lsa);
18860b8f172SDan Williams 
18960b8f172SDan Williams 	return rc;
19060b8f172SDan Williams }
19160b8f172SDan Williams 
cxl_pmem_nvdimm_ctl(struct nvdimm * nvdimm,unsigned int cmd,void * buf,unsigned int buf_len)19260b8f172SDan Williams static int cxl_pmem_nvdimm_ctl(struct nvdimm *nvdimm, unsigned int cmd,
19360b8f172SDan Williams 			       void *buf, unsigned int buf_len)
19460b8f172SDan Williams {
19560b8f172SDan Williams 	struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
19660b8f172SDan Williams 	unsigned long cmd_mask = nvdimm_cmd_mask(nvdimm);
19760b8f172SDan Williams 	struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
198*59f8d151SDan Williams 	struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
19960b8f172SDan Williams 
20060b8f172SDan Williams 	if (!test_bit(cmd, &cmd_mask))
20160b8f172SDan Williams 		return -ENOTTY;
20260b8f172SDan Williams 
20360b8f172SDan Williams 	switch (cmd) {
20460b8f172SDan Williams 	case ND_CMD_GET_CONFIG_SIZE:
205*59f8d151SDan Williams 		return cxl_pmem_get_config_size(mds, buf, buf_len);
20660b8f172SDan Williams 	case ND_CMD_GET_CONFIG_DATA:
207*59f8d151SDan Williams 		return cxl_pmem_get_config_data(mds, buf, buf_len);
20860b8f172SDan Williams 	case ND_CMD_SET_CONFIG_DATA:
209*59f8d151SDan Williams 		return cxl_pmem_set_config_data(mds, buf, buf_len);
21060b8f172SDan Williams 	default:
21160b8f172SDan Williams 		return -ENOTTY;
21260b8f172SDan Williams 	}
21360b8f172SDan Williams }
21460b8f172SDan Williams 
cxl_pmem_ctl(struct nvdimm_bus_descriptor * nd_desc,struct nvdimm * nvdimm,unsigned int cmd,void * buf,unsigned int buf_len,int * cmd_rc)2158fdcb170SDan Williams static int cxl_pmem_ctl(struct nvdimm_bus_descriptor *nd_desc,
2168fdcb170SDan Williams 			struct nvdimm *nvdimm, unsigned int cmd, void *buf,
2178fdcb170SDan Williams 			unsigned int buf_len, int *cmd_rc)
2188fdcb170SDan Williams {
21960b8f172SDan Williams 	/*
22060b8f172SDan Williams 	 * No firmware response to translate, let the transport error
22160b8f172SDan Williams 	 * code take precedence.
22260b8f172SDan Williams 	 */
22360b8f172SDan Williams 	*cmd_rc = 0;
22460b8f172SDan Williams 
22560b8f172SDan Williams 	if (!nvdimm)
2268fdcb170SDan Williams 		return -ENOTTY;
22760b8f172SDan Williams 	return cxl_pmem_nvdimm_ctl(nvdimm, cmd, buf, buf_len);
2288fdcb170SDan Williams }
2298fdcb170SDan Williams 
detach_nvdimm(struct device * dev,void * data)23019398821SDan Williams static int detach_nvdimm(struct device *dev, void *data)
23119398821SDan Williams {
23219398821SDan Williams 	struct cxl_nvdimm *cxl_nvd;
23319398821SDan Williams 	bool release = false;
23419398821SDan Williams 
23519398821SDan Williams 	if (!is_cxl_nvdimm(dev))
23619398821SDan Williams 		return 0;
23719398821SDan Williams 
23819398821SDan Williams 	device_lock(dev);
23919398821SDan Williams 	if (!dev->driver)
24019398821SDan Williams 		goto out;
24119398821SDan Williams 
24219398821SDan Williams 	cxl_nvd = to_cxl_nvdimm(dev);
24319398821SDan Williams 	if (cxl_nvd->cxlmd && cxl_nvd->cxlmd->cxl_nvb == data)
24419398821SDan Williams 		release = true;
24519398821SDan Williams out:
24619398821SDan Williams 	device_unlock(dev);
24719398821SDan Williams 	if (release)
24819398821SDan Williams 		device_release_driver(dev);
24919398821SDan Williams 	return 0;
25019398821SDan Williams }
25119398821SDan Williams 
unregister_nvdimm_bus(void * _cxl_nvb)252f17b558dSDan Williams static void unregister_nvdimm_bus(void *_cxl_nvb)
253f17b558dSDan Williams {
254f17b558dSDan Williams 	struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb;
255f17b558dSDan Williams 	struct nvdimm_bus *nvdimm_bus = cxl_nvb->nvdimm_bus;
256f17b558dSDan Williams 
25719398821SDan Williams 	bus_for_each_dev(&cxl_bus_type, NULL, cxl_nvb, detach_nvdimm);
25819398821SDan Williams 
259f17b558dSDan Williams 	cxl_nvb->nvdimm_bus = NULL;
260f17b558dSDan Williams 	nvdimm_bus_unregister(nvdimm_bus);
261f17b558dSDan Williams }
262f17b558dSDan Williams 
cxl_nvdimm_bridge_probe(struct device * dev)2638fdcb170SDan Williams static int cxl_nvdimm_bridge_probe(struct device *dev)
2648fdcb170SDan Williams {
2658fdcb170SDan Williams 	struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
2668fdcb170SDan Williams 
2678fdcb170SDan Williams 	cxl_nvb->nd_desc = (struct nvdimm_bus_descriptor) {
2688fdcb170SDan Williams 		.provider_name = "CXL",
2698fdcb170SDan Williams 		.module = THIS_MODULE,
2708fdcb170SDan Williams 		.ndctl = cxl_pmem_ctl,
2718fdcb170SDan Williams 	};
2728fdcb170SDan Williams 
273f17b558dSDan Williams 	cxl_nvb->nvdimm_bus =
274f17b558dSDan Williams 		nvdimm_bus_register(&cxl_nvb->dev, &cxl_nvb->nd_desc);
275f17b558dSDan Williams 
276f17b558dSDan Williams 	if (!cxl_nvb->nvdimm_bus)
277f17b558dSDan Williams 		return -ENOMEM;
278f17b558dSDan Williams 
279f17b558dSDan Williams 	return devm_add_action_or_reset(dev, unregister_nvdimm_bus, cxl_nvb);
2808fdcb170SDan Williams }
2818fdcb170SDan Williams 
2828fdcb170SDan Williams static struct cxl_driver cxl_nvdimm_bridge_driver = {
2838fdcb170SDan Williams 	.name = "cxl_nvdimm_bridge",
2848fdcb170SDan Williams 	.probe = cxl_nvdimm_bridge_probe,
2858fdcb170SDan Williams 	.id = CXL_DEVICE_NVDIMM_BRIDGE,
286cb9cfff8SDan Williams 	.drv = {
287cb9cfff8SDan Williams 		.suppress_bind_attrs = true,
288cb9cfff8SDan Williams 	},
2898fdcb170SDan Williams };
2908fdcb170SDan Williams 
unregister_nvdimm_region(void * nd_region)29104ad63f0SDan Williams static void unregister_nvdimm_region(void *nd_region)
29204ad63f0SDan Williams {
2934d07ae22SDan Williams 	nvdimm_region_delete(nd_region);
2944d07ae22SDan Williams }
29504ad63f0SDan Williams 
cxlr_pmem_remove_resource(void * res)29604ad63f0SDan Williams static void cxlr_pmem_remove_resource(void *res)
29704ad63f0SDan Williams {
29804ad63f0SDan Williams 	remove_resource(res);
29904ad63f0SDan Williams }
30004ad63f0SDan Williams 
30104ad63f0SDan Williams struct cxl_pmem_region_info {
30204ad63f0SDan Williams 	u64 offset;
30304ad63f0SDan Williams 	u64 serial;
30404ad63f0SDan Williams };
30504ad63f0SDan Williams 
cxl_pmem_region_probe(struct device * dev)30604ad63f0SDan Williams static int cxl_pmem_region_probe(struct device *dev)
30704ad63f0SDan Williams {
30804ad63f0SDan Williams 	struct nd_mapping_desc mappings[CXL_DECODER_MAX_INTERLEAVE];
30904ad63f0SDan Williams 	struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev);
31004ad63f0SDan Williams 	struct cxl_region *cxlr = cxlr_pmem->cxlr;
311f17b558dSDan Williams 	struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb;
31204ad63f0SDan Williams 	struct cxl_pmem_region_info *info = NULL;
31304ad63f0SDan Williams 	struct nd_interleave_set *nd_set;
31404ad63f0SDan Williams 	struct nd_region_desc ndr_desc;
31504ad63f0SDan Williams 	struct cxl_nvdimm *cxl_nvd;
31604ad63f0SDan Williams 	struct nvdimm *nvdimm;
31704ad63f0SDan Williams 	struct resource *res;
31804ad63f0SDan Williams 	int rc, i = 0;
31904ad63f0SDan Williams 
32004ad63f0SDan Williams 	memset(&mappings, 0, sizeof(mappings));
32104ad63f0SDan Williams 	memset(&ndr_desc, 0, sizeof(ndr_desc));
32204ad63f0SDan Williams 
32304ad63f0SDan Williams 	res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
324f17b558dSDan Williams 	if (!res)
325f17b558dSDan Williams 		return -ENOMEM;
32604ad63f0SDan Williams 
32704ad63f0SDan Williams 	res->name = "Persistent Memory";
32804ad63f0SDan Williams 	res->start = cxlr_pmem->hpa_range.start;
32904ad63f0SDan Williams 	res->end = cxlr_pmem->hpa_range.end;
33004ad63f0SDan Williams 	res->flags = IORESOURCE_MEM;
33104ad63f0SDan Williams 	res->desc = IORES_DESC_PERSISTENT_MEMORY;
33204ad63f0SDan Williams 
33304ad63f0SDan Williams 	rc = insert_resource(&iomem_resource, res);
33404ad63f0SDan Williams 	if (rc)
335f17b558dSDan Williams 		return rc;
33604ad63f0SDan Williams 
33704ad63f0SDan Williams 	rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);
33804ad63f0SDan Williams 	if (rc)
339f17b558dSDan Williams 		return rc;
34004ad63f0SDan Williams 
34104ad63f0SDan Williams 	ndr_desc.res = res;
34204ad63f0SDan Williams 	ndr_desc.provider_data = cxlr_pmem;
34304ad63f0SDan Williams 
34404ad63f0SDan Williams 	ndr_desc.numa_node = memory_add_physaddr_to_nid(res->start);
34504ad63f0SDan Williams 	ndr_desc.target_node = phys_to_target_node(res->start);
34604ad63f0SDan Williams 	if (ndr_desc.target_node == NUMA_NO_NODE) {
34704ad63f0SDan Williams 		ndr_desc.target_node = ndr_desc.numa_node;
34804ad63f0SDan Williams 		dev_dbg(&cxlr->dev, "changing target node from %d to %d",
34904ad63f0SDan Williams 			NUMA_NO_NODE, ndr_desc.target_node);
35004ad63f0SDan Williams 	}
35104ad63f0SDan Williams 
35204ad63f0SDan Williams 	nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
353f17b558dSDan Williams 	if (!nd_set)
354f17b558dSDan Williams 		return -ENOMEM;
35504ad63f0SDan Williams 
35604ad63f0SDan Williams 	ndr_desc.memregion = cxlr->id;
35704ad63f0SDan Williams 	set_bit(ND_REGION_CXL, &ndr_desc.flags);
35804ad63f0SDan Williams 	set_bit(ND_REGION_PERSIST_MEMCTRL, &ndr_desc.flags);
35904ad63f0SDan Williams 
36004ad63f0SDan Williams 	info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL);
361f17b558dSDan Williams 	if (!info)
362f17b558dSDan Williams 		return -ENOMEM;
36304ad63f0SDan Williams 
36404ad63f0SDan Williams 	for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
36504ad63f0SDan Williams 		struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
36604ad63f0SDan Williams 		struct cxl_memdev *cxlmd = m->cxlmd;
36704ad63f0SDan Williams 		struct cxl_dev_state *cxlds = cxlmd->cxlds;
36804ad63f0SDan Williams 
369f17b558dSDan Williams 		cxl_nvd = cxlmd->cxl_nvd;
37004ad63f0SDan Williams 		nvdimm = dev_get_drvdata(&cxl_nvd->dev);
37104ad63f0SDan Williams 		if (!nvdimm) {
37204ad63f0SDan Williams 			dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,
37304ad63f0SDan Williams 				dev_name(&cxlmd->dev));
37404ad63f0SDan Williams 			rc = -ENODEV;
3754d07ae22SDan Williams 			goto out_nvd;
37604ad63f0SDan Williams 		}
3774d07ae22SDan Williams 
37804ad63f0SDan Williams 		m->cxl_nvd = cxl_nvd;
37904ad63f0SDan Williams 		mappings[i] = (struct nd_mapping_desc) {
38004ad63f0SDan Williams 			.nvdimm = nvdimm,
38104ad63f0SDan Williams 			.start = m->start,
38204ad63f0SDan Williams 			.size = m->size,
38304ad63f0SDan Williams 			.position = i,
38404ad63f0SDan Williams 		};
38504ad63f0SDan Williams 		info[i].offset = m->start;
38604ad63f0SDan Williams 		info[i].serial = cxlds->serial;
38704ad63f0SDan Williams 	}
38804ad63f0SDan Williams 	ndr_desc.num_mappings = cxlr_pmem->nr_mappings;
38904ad63f0SDan Williams 	ndr_desc.mapping = mappings;
39004ad63f0SDan Williams 
39104ad63f0SDan Williams 	/*
39204ad63f0SDan Williams 	 * TODO enable CXL labels which skip the need for 'interleave-set cookie'
39304ad63f0SDan Williams 	 */
39404ad63f0SDan Williams 	nd_set->cookie1 =
39504ad63f0SDan Williams 		nd_fletcher64(info, sizeof(*info) * cxlr_pmem->nr_mappings, 0);
39604ad63f0SDan Williams 	nd_set->cookie2 = nd_set->cookie1;
39704ad63f0SDan Williams 	ndr_desc.nd_set = nd_set;
39804ad63f0SDan Williams 
39904ad63f0SDan Williams 	cxlr_pmem->nd_region =
40004ad63f0SDan Williams 		nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);
4019fd2cf4dSDan Carpenter 	if (!cxlr_pmem->nd_region) {
4029fd2cf4dSDan Carpenter 		rc = -ENOMEM;
4034d07ae22SDan Williams 		goto out_nvd;
40404ad63f0SDan Williams 	}
40504ad63f0SDan Williams 
40604ad63f0SDan Williams 	rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,
40704ad63f0SDan Williams 				      cxlr_pmem->nd_region);
4084d07ae22SDan Williams out_nvd:
40904ad63f0SDan Williams 	kfree(info);
41004ad63f0SDan Williams 
41104ad63f0SDan Williams 	return rc;
41204ad63f0SDan Williams }
41304ad63f0SDan Williams 
41404ad63f0SDan Williams static struct cxl_driver cxl_pmem_region_driver = {
41504ad63f0SDan Williams 	.name = "cxl_pmem_region",
41604ad63f0SDan Williams 	.probe = cxl_pmem_region_probe,
41704ad63f0SDan Williams 	.id = CXL_DEVICE_PMEM_REGION,
418cb9cfff8SDan Williams 	.drv = {
419cb9cfff8SDan Williams 		.suppress_bind_attrs = true,
420cb9cfff8SDan Williams 	},
42104ad63f0SDan Williams };
42204ad63f0SDan Williams 
cxl_pmem_init(void)4238fdcb170SDan Williams static __init int cxl_pmem_init(void)
4248fdcb170SDan Williams {
4258fdcb170SDan Williams 	int rc;
4268fdcb170SDan Williams 
42712f3856aSDan Williams 	set_bit(CXL_MEM_COMMAND_ID_SET_SHUTDOWN_STATE, exclusive_cmds);
42812f3856aSDan Williams 	set_bit(CXL_MEM_COMMAND_ID_SET_LSA, exclusive_cmds);
42912f3856aSDan Williams 
4308fdcb170SDan Williams 	rc = cxl_driver_register(&cxl_nvdimm_bridge_driver);
4318fdcb170SDan Williams 	if (rc)
43203ff079aSDan Williams 		return rc;
43321083f51SDan Williams 
43421083f51SDan Williams 	rc = cxl_driver_register(&cxl_nvdimm_driver);
43521083f51SDan Williams 	if (rc)
43621083f51SDan Williams 		goto err_nvdimm;
4378fdcb170SDan Williams 
43804ad63f0SDan Williams 	rc = cxl_driver_register(&cxl_pmem_region_driver);
43904ad63f0SDan Williams 	if (rc)
44004ad63f0SDan Williams 		goto err_region;
44104ad63f0SDan Williams 
4428fdcb170SDan Williams 	return 0;
4438fdcb170SDan Williams 
44404ad63f0SDan Williams err_region:
44504ad63f0SDan Williams 	cxl_driver_unregister(&cxl_nvdimm_driver);
44621083f51SDan Williams err_nvdimm:
44721083f51SDan Williams 	cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
4488fdcb170SDan Williams 	return rc;
4498fdcb170SDan Williams }
4508fdcb170SDan Williams 
cxl_pmem_exit(void)4518fdcb170SDan Williams static __exit void cxl_pmem_exit(void)
4528fdcb170SDan Williams {
45304ad63f0SDan Williams 	cxl_driver_unregister(&cxl_pmem_region_driver);
45421083f51SDan Williams 	cxl_driver_unregister(&cxl_nvdimm_driver);
4558fdcb170SDan Williams 	cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
4568fdcb170SDan Williams }
4578fdcb170SDan Williams 
4588fdcb170SDan Williams MODULE_LICENSE("GPL v2");
4598fdcb170SDan Williams module_init(cxl_pmem_init);
4608fdcb170SDan Williams module_exit(cxl_pmem_exit);
4618fdcb170SDan Williams MODULE_IMPORT_NS(CXL);
4628fdcb170SDan Williams MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
46321083f51SDan Williams MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM);
46404ad63f0SDan Williams MODULE_ALIAS_CXL(CXL_DEVICE_PMEM_REGION);
465