xref: /openbmc/linux/drivers/nvdimm/dimm_devs.c (revision 87a30e1f)
15b497af4SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2e6dfb2deSDan Williams /*
3e6dfb2deSDan Williams  * Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
4e6dfb2deSDan Williams  */
5e6dfb2deSDan Williams #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
6d5d30d5aSDan Williams #include <linux/moduleparam.h>
74d88a97aSDan Williams #include <linux/vmalloc.h>
8e6dfb2deSDan Williams #include <linux/device.h>
962232e45SDan Williams #include <linux/ndctl.h>
10e6dfb2deSDan Williams #include <linux/slab.h>
11e6dfb2deSDan Williams #include <linux/io.h>
12e6dfb2deSDan Williams #include <linux/fs.h>
13e6dfb2deSDan Williams #include <linux/mm.h>
14e6dfb2deSDan Williams #include "nd-core.h"
150ba1c634SDan Williams #include "label.h"
16ca6a4657SDan Williams #include "pmem.h"
174d88a97aSDan Williams #include "nd.h"
18e6dfb2deSDan Williams 
19e6dfb2deSDan Williams static DEFINE_IDA(dimm_ida);
20e6dfb2deSDan Williams 
21d5d30d5aSDan Williams static bool noblk;
22d5d30d5aSDan Williams module_param(noblk, bool, 0444);
23d5d30d5aSDan Williams MODULE_PARM_DESC(noblk, "force disable BLK / local alias support");
24d5d30d5aSDan Williams 
254d88a97aSDan Williams /*
264d88a97aSDan Williams  * Retrieve bus and dimm handle and return if this bus supports
274d88a97aSDan Williams  * get_config_data commands
284d88a97aSDan Williams  */
29aee65987SToshi Kani int nvdimm_check_config_data(struct device *dev)
304d88a97aSDan Williams {
31aee65987SToshi Kani 	struct nvdimm *nvdimm = to_nvdimm(dev);
324d88a97aSDan Williams 
33aee65987SToshi Kani 	if (!nvdimm->cmd_mask ||
34aee65987SToshi Kani 	    !test_bit(ND_CMD_GET_CONFIG_DATA, &nvdimm->cmd_mask)) {
358f078b38SDan Williams 		if (test_bit(NDD_ALIASING, &nvdimm->flags))
364d88a97aSDan Williams 			return -ENXIO;
37aee65987SToshi Kani 		else
38aee65987SToshi Kani 			return -ENOTTY;
39aee65987SToshi Kani 	}
404d88a97aSDan Williams 
414d88a97aSDan Williams 	return 0;
424d88a97aSDan Williams }
434d88a97aSDan Williams 
444d88a97aSDan Williams static int validate_dimm(struct nvdimm_drvdata *ndd)
454d88a97aSDan Williams {
46aee65987SToshi Kani 	int rc;
474d88a97aSDan Williams 
48aee65987SToshi Kani 	if (!ndd)
49aee65987SToshi Kani 		return -EINVAL;
50aee65987SToshi Kani 
51aee65987SToshi Kani 	rc = nvdimm_check_config_data(ndd->dev);
52aee65987SToshi Kani 	if (rc)
53d75f773cSSakari Ailus 		dev_dbg(ndd->dev, "%ps: %s error: %d\n",
544d88a97aSDan Williams 				__builtin_return_address(0), __func__, rc);
554d88a97aSDan Williams 	return rc;
564d88a97aSDan Williams }
574d88a97aSDan Williams 
584d88a97aSDan Williams /**
594d88a97aSDan Williams  * nvdimm_init_nsarea - determine the geometry of a dimm's namespace area
604d88a97aSDan Williams  * @nvdimm: dimm to initialize
614d88a97aSDan Williams  */
624d88a97aSDan Williams int nvdimm_init_nsarea(struct nvdimm_drvdata *ndd)
634d88a97aSDan Williams {
644d88a97aSDan Williams 	struct nd_cmd_get_config_size *cmd = &ndd->nsarea;
654d88a97aSDan Williams 	struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
664d88a97aSDan Williams 	struct nvdimm_bus_descriptor *nd_desc;
674d88a97aSDan Williams 	int rc = validate_dimm(ndd);
689d62ed96SDan Williams 	int cmd_rc = 0;
694d88a97aSDan Williams 
704d88a97aSDan Williams 	if (rc)
714d88a97aSDan Williams 		return rc;
724d88a97aSDan Williams 
734d88a97aSDan Williams 	if (cmd->config_size)
744d88a97aSDan Williams 		return 0; /* already valid */
754d88a97aSDan Williams 
764d88a97aSDan Williams 	memset(cmd, 0, sizeof(*cmd));
774d88a97aSDan Williams 	nd_desc = nvdimm_bus->nd_desc;
789d62ed96SDan Williams 	rc = nd_desc->ndctl(nd_desc, to_nvdimm(ndd->dev),
799d62ed96SDan Williams 			ND_CMD_GET_CONFIG_SIZE, cmd, sizeof(*cmd), &cmd_rc);
809d62ed96SDan Williams 	if (rc < 0)
819d62ed96SDan Williams 		return rc;
829d62ed96SDan Williams 	return cmd_rc;
834d88a97aSDan Williams }
844d88a97aSDan Williams 
852d657d17SAlexander Duyck int nvdimm_get_config_data(struct nvdimm_drvdata *ndd, void *buf,
862d657d17SAlexander Duyck 			   size_t offset, size_t len)
874d88a97aSDan Williams {
884d88a97aSDan Williams 	struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
892d657d17SAlexander Duyck 	struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
90e7c5a571SDan Williams 	int rc = validate_dimm(ndd), cmd_rc = 0;
914d88a97aSDan Williams 	struct nd_cmd_get_config_data_hdr *cmd;
922d657d17SAlexander Duyck 	size_t max_cmd_size, buf_offset;
934d88a97aSDan Williams 
944d88a97aSDan Williams 	if (rc)
954d88a97aSDan Williams 		return rc;
964d88a97aSDan Williams 
972d657d17SAlexander Duyck 	if (offset + len > ndd->nsarea.config_size)
984d88a97aSDan Williams 		return -ENXIO;
994d88a97aSDan Williams 
1002d657d17SAlexander Duyck 	max_cmd_size = min_t(u32, len, ndd->nsarea.max_xfer);
101d11cf4a7SDan Williams 	cmd = kvzalloc(max_cmd_size + sizeof(*cmd), GFP_KERNEL);
1024d88a97aSDan Williams 	if (!cmd)
1034d88a97aSDan Williams 		return -ENOMEM;
1044d88a97aSDan Williams 
1052d657d17SAlexander Duyck 	for (buf_offset = 0; len;
1062d657d17SAlexander Duyck 	     len -= cmd->in_length, buf_offset += cmd->in_length) {
1072d657d17SAlexander Duyck 		size_t cmd_size;
1082d657d17SAlexander Duyck 
1092d657d17SAlexander Duyck 		cmd->in_offset = offset + buf_offset;
1102d657d17SAlexander Duyck 		cmd->in_length = min(max_cmd_size, len);
1112d657d17SAlexander Duyck 
1122d657d17SAlexander Duyck 		cmd_size = sizeof(*cmd) + cmd->in_length;
1132d657d17SAlexander Duyck 
1144d88a97aSDan Williams 		rc = nd_desc->ndctl(nd_desc, to_nvdimm(ndd->dev),
1152d657d17SAlexander Duyck 				ND_CMD_GET_CONFIG_DATA, cmd, cmd_size, &cmd_rc);
116e7c5a571SDan Williams 		if (rc < 0)
117e7c5a571SDan Williams 			break;
118e7c5a571SDan Williams 		if (cmd_rc < 0) {
119e7c5a571SDan Williams 			rc = cmd_rc;
1204d88a97aSDan Williams 			break;
1214d88a97aSDan Williams 		}
1222d657d17SAlexander Duyck 
1232d657d17SAlexander Duyck 		/* out_buf should be valid, copy it into our output buffer */
1242d657d17SAlexander Duyck 		memcpy(buf + buf_offset, cmd->out_buf, cmd->in_length);
1254d88a97aSDan Williams 	}
126d11cf4a7SDan Williams 	kvfree(cmd);
1274d88a97aSDan Williams 
1284d88a97aSDan Williams 	return rc;
1294d88a97aSDan Williams }
1304d88a97aSDan Williams 
131f524bf27SDan Williams int nvdimm_set_config_data(struct nvdimm_drvdata *ndd, size_t offset,
132f524bf27SDan Williams 		void *buf, size_t len)
133f524bf27SDan Williams {
134f524bf27SDan Williams 	size_t max_cmd_size, buf_offset;
135f524bf27SDan Williams 	struct nd_cmd_set_config_hdr *cmd;
136e7c5a571SDan Williams 	int rc = validate_dimm(ndd), cmd_rc = 0;
137f524bf27SDan Williams 	struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
138f524bf27SDan Williams 	struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
139f524bf27SDan Williams 
140f524bf27SDan Williams 	if (rc)
141f524bf27SDan Williams 		return rc;
142f524bf27SDan Williams 
143f524bf27SDan Williams 	if (offset + len > ndd->nsarea.config_size)
144f524bf27SDan Williams 		return -ENXIO;
145f524bf27SDan Williams 
146d11cf4a7SDan Williams 	max_cmd_size = min_t(u32, len, ndd->nsarea.max_xfer);
147d11cf4a7SDan Williams 	cmd = kvzalloc(max_cmd_size + sizeof(*cmd) + sizeof(u32), GFP_KERNEL);
148f524bf27SDan Williams 	if (!cmd)
149f524bf27SDan Williams 		return -ENOMEM;
150f524bf27SDan Williams 
151f524bf27SDan Williams 	for (buf_offset = 0; len; len -= cmd->in_length,
152f524bf27SDan Williams 			buf_offset += cmd->in_length) {
153f524bf27SDan Williams 		size_t cmd_size;
154f524bf27SDan Williams 
155f524bf27SDan Williams 		cmd->in_offset = offset + buf_offset;
156f524bf27SDan Williams 		cmd->in_length = min(max_cmd_size, len);
157f524bf27SDan Williams 		memcpy(cmd->in_buf, buf + buf_offset, cmd->in_length);
158f524bf27SDan Williams 
159f524bf27SDan Williams 		/* status is output in the last 4-bytes of the command buffer */
160f524bf27SDan Williams 		cmd_size = sizeof(*cmd) + cmd->in_length + sizeof(u32);
161f524bf27SDan Williams 
162f524bf27SDan Williams 		rc = nd_desc->ndctl(nd_desc, to_nvdimm(ndd->dev),
163e7c5a571SDan Williams 				ND_CMD_SET_CONFIG_DATA, cmd, cmd_size, &cmd_rc);
164e7c5a571SDan Williams 		if (rc < 0)
165e7c5a571SDan Williams 			break;
166e7c5a571SDan Williams 		if (cmd_rc < 0) {
167e7c5a571SDan Williams 			rc = cmd_rc;
168f524bf27SDan Williams 			break;
169f524bf27SDan Williams 		}
170f524bf27SDan Williams 	}
171d11cf4a7SDan Williams 	kvfree(cmd);
172f524bf27SDan Williams 
173f524bf27SDan Williams 	return rc;
174f524bf27SDan Williams }
175f524bf27SDan Williams 
17642237e39SDan Williams void nvdimm_set_aliasing(struct device *dev)
17742237e39SDan Williams {
17842237e39SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
17942237e39SDan Williams 
1808f078b38SDan Williams 	set_bit(NDD_ALIASING, &nvdimm->flags);
1818f078b38SDan Williams }
1828f078b38SDan Williams 
1838f078b38SDan Williams void nvdimm_set_locked(struct device *dev)
1848f078b38SDan Williams {
1858f078b38SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
1868f078b38SDan Williams 
1878f078b38SDan Williams 	set_bit(NDD_LOCKED, &nvdimm->flags);
18842237e39SDan Williams }
18942237e39SDan Williams 
190d34cb808SDan Williams void nvdimm_clear_locked(struct device *dev)
191d34cb808SDan Williams {
192d34cb808SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
193d34cb808SDan Williams 
194d34cb808SDan Williams 	clear_bit(NDD_LOCKED, &nvdimm->flags);
195d34cb808SDan Williams }
196d34cb808SDan Williams 
197e6dfb2deSDan Williams static void nvdimm_release(struct device *dev)
198e6dfb2deSDan Williams {
199e6dfb2deSDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
200e6dfb2deSDan Williams 
201e6dfb2deSDan Williams 	ida_simple_remove(&dimm_ida, nvdimm->id);
202e6dfb2deSDan Williams 	kfree(nvdimm);
203e6dfb2deSDan Williams }
204e6dfb2deSDan Williams 
205e6dfb2deSDan Williams static struct device_type nvdimm_device_type = {
206e6dfb2deSDan Williams 	.name = "nvdimm",
207e6dfb2deSDan Williams 	.release = nvdimm_release,
208e6dfb2deSDan Williams };
209e6dfb2deSDan Williams 
21062232e45SDan Williams bool is_nvdimm(struct device *dev)
211e6dfb2deSDan Williams {
212e6dfb2deSDan Williams 	return dev->type == &nvdimm_device_type;
213e6dfb2deSDan Williams }
214e6dfb2deSDan Williams 
215e6dfb2deSDan Williams struct nvdimm *to_nvdimm(struct device *dev)
216e6dfb2deSDan Williams {
217e6dfb2deSDan Williams 	struct nvdimm *nvdimm = container_of(dev, struct nvdimm, dev);
218e6dfb2deSDan Williams 
219e6dfb2deSDan Williams 	WARN_ON(!is_nvdimm(dev));
220e6dfb2deSDan Williams 	return nvdimm;
221e6dfb2deSDan Williams }
222e6dfb2deSDan Williams EXPORT_SYMBOL_GPL(to_nvdimm);
223e6dfb2deSDan Williams 
224047fc8a1SRoss Zwisler struct nvdimm *nd_blk_region_to_dimm(struct nd_blk_region *ndbr)
225047fc8a1SRoss Zwisler {
226047fc8a1SRoss Zwisler 	struct nd_region *nd_region = &ndbr->nd_region;
227047fc8a1SRoss Zwisler 	struct nd_mapping *nd_mapping = &nd_region->mapping[0];
228047fc8a1SRoss Zwisler 
229047fc8a1SRoss Zwisler 	return nd_mapping->nvdimm;
230047fc8a1SRoss Zwisler }
231047fc8a1SRoss Zwisler EXPORT_SYMBOL_GPL(nd_blk_region_to_dimm);
232047fc8a1SRoss Zwisler 
233ca6a4657SDan Williams unsigned long nd_blk_memremap_flags(struct nd_blk_region *ndbr)
234ca6a4657SDan Williams {
235ca6a4657SDan Williams 	/* pmem mapping properties are private to libnvdimm */
236ca6a4657SDan Williams 	return ARCH_MEMREMAP_PMEM;
237ca6a4657SDan Williams }
238ca6a4657SDan Williams EXPORT_SYMBOL_GPL(nd_blk_memremap_flags);
239ca6a4657SDan Williams 
240bf9bccc1SDan Williams struct nvdimm_drvdata *to_ndd(struct nd_mapping *nd_mapping)
241bf9bccc1SDan Williams {
242bf9bccc1SDan Williams 	struct nvdimm *nvdimm = nd_mapping->nvdimm;
243bf9bccc1SDan Williams 
244bf9bccc1SDan Williams 	WARN_ON_ONCE(!is_nvdimm_bus_locked(&nvdimm->dev));
245bf9bccc1SDan Williams 
246bf9bccc1SDan Williams 	return dev_get_drvdata(&nvdimm->dev);
247bf9bccc1SDan Williams }
248bf9bccc1SDan Williams EXPORT_SYMBOL(to_ndd);
249bf9bccc1SDan Williams 
250bf9bccc1SDan Williams void nvdimm_drvdata_release(struct kref *kref)
251bf9bccc1SDan Williams {
252bf9bccc1SDan Williams 	struct nvdimm_drvdata *ndd = container_of(kref, typeof(*ndd), kref);
253bf9bccc1SDan Williams 	struct device *dev = ndd->dev;
254bf9bccc1SDan Williams 	struct resource *res, *_r;
255bf9bccc1SDan Williams 
256426824d6SDan Williams 	dev_dbg(dev, "trace\n");
257bf9bccc1SDan Williams 	nvdimm_bus_lock(dev);
258bf9bccc1SDan Williams 	for_each_dpa_resource_safe(ndd, res, _r)
259bf9bccc1SDan Williams 		nvdimm_free_dpa(ndd, res);
260bf9bccc1SDan Williams 	nvdimm_bus_unlock(dev);
261bf9bccc1SDan Williams 
262a06a7576Syalin wang 	kvfree(ndd->data);
263bf9bccc1SDan Williams 	kfree(ndd);
264bf9bccc1SDan Williams 	put_device(dev);
265bf9bccc1SDan Williams }
266bf9bccc1SDan Williams 
267bf9bccc1SDan Williams void get_ndd(struct nvdimm_drvdata *ndd)
268bf9bccc1SDan Williams {
269bf9bccc1SDan Williams 	kref_get(&ndd->kref);
270bf9bccc1SDan Williams }
271bf9bccc1SDan Williams 
272bf9bccc1SDan Williams void put_ndd(struct nvdimm_drvdata *ndd)
273bf9bccc1SDan Williams {
274bf9bccc1SDan Williams 	if (ndd)
275bf9bccc1SDan Williams 		kref_put(&ndd->kref, nvdimm_drvdata_release);
276bf9bccc1SDan Williams }
277bf9bccc1SDan Williams 
278e6dfb2deSDan Williams const char *nvdimm_name(struct nvdimm *nvdimm)
279e6dfb2deSDan Williams {
280e6dfb2deSDan Williams 	return dev_name(&nvdimm->dev);
281e6dfb2deSDan Williams }
282e6dfb2deSDan Williams EXPORT_SYMBOL_GPL(nvdimm_name);
283e6dfb2deSDan Williams 
284ba9c8dd3SDan Williams struct kobject *nvdimm_kobj(struct nvdimm *nvdimm)
285ba9c8dd3SDan Williams {
286ba9c8dd3SDan Williams 	return &nvdimm->dev.kobj;
287ba9c8dd3SDan Williams }
288ba9c8dd3SDan Williams EXPORT_SYMBOL_GPL(nvdimm_kobj);
289ba9c8dd3SDan Williams 
290e3654ecaSDan Williams unsigned long nvdimm_cmd_mask(struct nvdimm *nvdimm)
291e3654ecaSDan Williams {
292e3654ecaSDan Williams 	return nvdimm->cmd_mask;
293e3654ecaSDan Williams }
294e3654ecaSDan Williams EXPORT_SYMBOL_GPL(nvdimm_cmd_mask);
295e3654ecaSDan Williams 
296e6dfb2deSDan Williams void *nvdimm_provider_data(struct nvdimm *nvdimm)
297e6dfb2deSDan Williams {
29862232e45SDan Williams 	if (nvdimm)
299e6dfb2deSDan Williams 		return nvdimm->provider_data;
30062232e45SDan Williams 	return NULL;
301e6dfb2deSDan Williams }
302e6dfb2deSDan Williams EXPORT_SYMBOL_GPL(nvdimm_provider_data);
303e6dfb2deSDan Williams 
30462232e45SDan Williams static ssize_t commands_show(struct device *dev,
30562232e45SDan Williams 		struct device_attribute *attr, char *buf)
30662232e45SDan Williams {
30762232e45SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
30862232e45SDan Williams 	int cmd, len = 0;
30962232e45SDan Williams 
310e3654ecaSDan Williams 	if (!nvdimm->cmd_mask)
31162232e45SDan Williams 		return sprintf(buf, "\n");
31262232e45SDan Williams 
313e3654ecaSDan Williams 	for_each_set_bit(cmd, &nvdimm->cmd_mask, BITS_PER_LONG)
31462232e45SDan Williams 		len += sprintf(buf + len, "%s ", nvdimm_cmd_name(cmd));
31562232e45SDan Williams 	len += sprintf(buf + len, "\n");
31662232e45SDan Williams 	return len;
31762232e45SDan Williams }
31862232e45SDan Williams static DEVICE_ATTR_RO(commands);
31962232e45SDan Williams 
320efbf6f50SDan Williams static ssize_t flags_show(struct device *dev,
321efbf6f50SDan Williams 		struct device_attribute *attr, char *buf)
322efbf6f50SDan Williams {
323efbf6f50SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
324efbf6f50SDan Williams 
325efbf6f50SDan Williams 	return sprintf(buf, "%s%s\n",
326efbf6f50SDan Williams 			test_bit(NDD_ALIASING, &nvdimm->flags) ? "alias " : "",
327efbf6f50SDan Williams 			test_bit(NDD_LOCKED, &nvdimm->flags) ? "lock " : "");
328efbf6f50SDan Williams }
329efbf6f50SDan Williams static DEVICE_ATTR_RO(flags);
330efbf6f50SDan Williams 
331eaf96153SDan Williams static ssize_t state_show(struct device *dev, struct device_attribute *attr,
332eaf96153SDan Williams 		char *buf)
333eaf96153SDan Williams {
334eaf96153SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
335eaf96153SDan Williams 
336eaf96153SDan Williams 	/*
337eaf96153SDan Williams 	 * The state may be in the process of changing, userspace should
338eaf96153SDan Williams 	 * quiesce probing if it wants a static answer
339eaf96153SDan Williams 	 */
340eaf96153SDan Williams 	nvdimm_bus_lock(dev);
341eaf96153SDan Williams 	nvdimm_bus_unlock(dev);
342eaf96153SDan Williams 	return sprintf(buf, "%s\n", atomic_read(&nvdimm->busy)
343eaf96153SDan Williams 			? "active" : "idle");
344eaf96153SDan Williams }
345eaf96153SDan Williams static DEVICE_ATTR_RO(state);
346eaf96153SDan Williams 
3470ba1c634SDan Williams static ssize_t available_slots_show(struct device *dev,
3480ba1c634SDan Williams 		struct device_attribute *attr, char *buf)
3490ba1c634SDan Williams {
3500ba1c634SDan Williams 	struct nvdimm_drvdata *ndd = dev_get_drvdata(dev);
3510ba1c634SDan Williams 	ssize_t rc;
3520ba1c634SDan Williams 	u32 nfree;
3530ba1c634SDan Williams 
3540ba1c634SDan Williams 	if (!ndd)
3550ba1c634SDan Williams 		return -ENXIO;
3560ba1c634SDan Williams 
3570ba1c634SDan Williams 	nvdimm_bus_lock(dev);
3580ba1c634SDan Williams 	nfree = nd_label_nfree(ndd);
3590ba1c634SDan Williams 	if (nfree - 1 > nfree) {
3600ba1c634SDan Williams 		dev_WARN_ONCE(dev, 1, "we ate our last label?\n");
3610ba1c634SDan Williams 		nfree = 0;
3620ba1c634SDan Williams 	} else
3630ba1c634SDan Williams 		nfree--;
3640ba1c634SDan Williams 	rc = sprintf(buf, "%d\n", nfree);
3650ba1c634SDan Williams 	nvdimm_bus_unlock(dev);
3660ba1c634SDan Williams 	return rc;
3670ba1c634SDan Williams }
3680ba1c634SDan Williams static DEVICE_ATTR_RO(available_slots);
3690ba1c634SDan Williams 
3703c13e2acSDave Jiang __weak ssize_t security_show(struct device *dev,
371f2989396SDave Jiang 		struct device_attribute *attr, char *buf)
372f2989396SDave Jiang {
373f2989396SDave Jiang 	struct nvdimm *nvdimm = to_nvdimm(dev);
374f2989396SDave Jiang 
375f2989396SDave Jiang 	switch (nvdimm->sec.state) {
376f2989396SDave Jiang 	case NVDIMM_SECURITY_DISABLED:
377f2989396SDave Jiang 		return sprintf(buf, "disabled\n");
378f2989396SDave Jiang 	case NVDIMM_SECURITY_UNLOCKED:
379f2989396SDave Jiang 		return sprintf(buf, "unlocked\n");
380f2989396SDave Jiang 	case NVDIMM_SECURITY_LOCKED:
381f2989396SDave Jiang 		return sprintf(buf, "locked\n");
382f2989396SDave Jiang 	case NVDIMM_SECURITY_FROZEN:
383f2989396SDave Jiang 		return sprintf(buf, "frozen\n");
384f2989396SDave Jiang 	case NVDIMM_SECURITY_OVERWRITE:
385f2989396SDave Jiang 		return sprintf(buf, "overwrite\n");
38689fa9d8eSDave Jiang 	default:
38789fa9d8eSDave Jiang 		return -ENOTTY;
388f2989396SDave Jiang 	}
389f2989396SDave Jiang 
390f2989396SDave Jiang 	return -ENOTTY;
391f2989396SDave Jiang }
39237833fb7SDave Jiang 
39303b65b22SDave Jiang #define OPS							\
39403b65b22SDave Jiang 	C( OP_FREEZE,		"freeze",		1),	\
395d2a4ac73SDave Jiang 	C( OP_DISABLE,		"disable",		2),	\
39664e77c8cSDave Jiang 	C( OP_UPDATE,		"update",		3),	\
3977d988097SDave Jiang 	C( OP_ERASE,		"erase",		2),	\
39889fa9d8eSDave Jiang 	C( OP_OVERWRITE,	"overwrite",		2),	\
39989fa9d8eSDave Jiang 	C( OP_MASTER_UPDATE,	"master_update",	3),	\
40089fa9d8eSDave Jiang 	C( OP_MASTER_ERASE,	"master_erase",		2)
40103b65b22SDave Jiang #undef C
40203b65b22SDave Jiang #define C(a, b, c) a
40303b65b22SDave Jiang enum nvdimmsec_op_ids { OPS };
40403b65b22SDave Jiang #undef C
40503b65b22SDave Jiang #define C(a, b, c) { b, c }
40603b65b22SDave Jiang static struct {
40703b65b22SDave Jiang 	const char *name;
40803b65b22SDave Jiang 	int args;
40903b65b22SDave Jiang } ops[] = { OPS };
41003b65b22SDave Jiang #undef C
41103b65b22SDave Jiang 
41203b65b22SDave Jiang #define SEC_CMD_SIZE 32
41303b65b22SDave Jiang #define KEY_ID_SIZE 10
41403b65b22SDave Jiang 
41537833fb7SDave Jiang static ssize_t __security_store(struct device *dev, const char *buf, size_t len)
41637833fb7SDave Jiang {
41737833fb7SDave Jiang 	struct nvdimm *nvdimm = to_nvdimm(dev);
41837833fb7SDave Jiang 	ssize_t rc;
41903b65b22SDave Jiang 	char cmd[SEC_CMD_SIZE+1], keystr[KEY_ID_SIZE+1],
42003b65b22SDave Jiang 		nkeystr[KEY_ID_SIZE+1];
42103b65b22SDave Jiang 	unsigned int key, newkey;
42203b65b22SDave Jiang 	int i;
42337833fb7SDave Jiang 
42437833fb7SDave Jiang 	if (atomic_read(&nvdimm->busy))
42537833fb7SDave Jiang 		return -EBUSY;
42637833fb7SDave Jiang 
42703b65b22SDave Jiang 	rc = sscanf(buf, "%"__stringify(SEC_CMD_SIZE)"s"
42803b65b22SDave Jiang 			" %"__stringify(KEY_ID_SIZE)"s"
42903b65b22SDave Jiang 			" %"__stringify(KEY_ID_SIZE)"s",
43003b65b22SDave Jiang 			cmd, keystr, nkeystr);
43103b65b22SDave Jiang 	if (rc < 1)
43203b65b22SDave Jiang 		return -EINVAL;
43303b65b22SDave Jiang 	for (i = 0; i < ARRAY_SIZE(ops); i++)
43403b65b22SDave Jiang 		if (sysfs_streq(cmd, ops[i].name))
43503b65b22SDave Jiang 			break;
43603b65b22SDave Jiang 	if (i >= ARRAY_SIZE(ops))
43703b65b22SDave Jiang 		return -EINVAL;
43803b65b22SDave Jiang 	if (ops[i].args > 1)
43903b65b22SDave Jiang 		rc = kstrtouint(keystr, 0, &key);
44003b65b22SDave Jiang 	if (rc >= 0 && ops[i].args > 2)
44103b65b22SDave Jiang 		rc = kstrtouint(nkeystr, 0, &newkey);
44203b65b22SDave Jiang 	if (rc < 0)
44303b65b22SDave Jiang 		return rc;
44403b65b22SDave Jiang 
44503b65b22SDave Jiang 	if (i == OP_FREEZE) {
44637833fb7SDave Jiang 		dev_dbg(dev, "freeze\n");
44737833fb7SDave Jiang 		rc = nvdimm_security_freeze(nvdimm);
44803b65b22SDave Jiang 	} else if (i == OP_DISABLE) {
44903b65b22SDave Jiang 		dev_dbg(dev, "disable %u\n", key);
45003b65b22SDave Jiang 		rc = nvdimm_security_disable(nvdimm, key);
451d2a4ac73SDave Jiang 	} else if (i == OP_UPDATE) {
452d2a4ac73SDave Jiang 		dev_dbg(dev, "update %u %u\n", key, newkey);
45389fa9d8eSDave Jiang 		rc = nvdimm_security_update(nvdimm, key, newkey, NVDIMM_USER);
45464e77c8cSDave Jiang 	} else if (i == OP_ERASE) {
45564e77c8cSDave Jiang 		dev_dbg(dev, "erase %u\n", key);
45689fa9d8eSDave Jiang 		rc = nvdimm_security_erase(nvdimm, key, NVDIMM_USER);
4577d988097SDave Jiang 	} else if (i == OP_OVERWRITE) {
4587d988097SDave Jiang 		dev_dbg(dev, "overwrite %u\n", key);
4597d988097SDave Jiang 		rc = nvdimm_security_overwrite(nvdimm, key);
46089fa9d8eSDave Jiang 	} else if (i == OP_MASTER_UPDATE) {
46189fa9d8eSDave Jiang 		dev_dbg(dev, "master_update %u %u\n", key, newkey);
46289fa9d8eSDave Jiang 		rc = nvdimm_security_update(nvdimm, key, newkey,
46389fa9d8eSDave Jiang 				NVDIMM_MASTER);
46489fa9d8eSDave Jiang 	} else if (i == OP_MASTER_ERASE) {
46589fa9d8eSDave Jiang 		dev_dbg(dev, "master_erase %u\n", key);
46689fa9d8eSDave Jiang 		rc = nvdimm_security_erase(nvdimm, key,
46789fa9d8eSDave Jiang 				NVDIMM_MASTER);
46837833fb7SDave Jiang 	} else
46937833fb7SDave Jiang 		return -EINVAL;
47037833fb7SDave Jiang 
47137833fb7SDave Jiang 	if (rc == 0)
47237833fb7SDave Jiang 		rc = len;
47337833fb7SDave Jiang 	return rc;
47437833fb7SDave Jiang }
47537833fb7SDave Jiang 
47637833fb7SDave Jiang static ssize_t security_store(struct device *dev,
47737833fb7SDave Jiang 		struct device_attribute *attr, const char *buf, size_t len)
47837833fb7SDave Jiang 
47937833fb7SDave Jiang {
48037833fb7SDave Jiang 	ssize_t rc;
48137833fb7SDave Jiang 
48237833fb7SDave Jiang 	/*
48337833fb7SDave Jiang 	 * Require all userspace triggered security management to be
48437833fb7SDave Jiang 	 * done while probing is idle and the DIMM is not in active use
48537833fb7SDave Jiang 	 * in any region.
48637833fb7SDave Jiang 	 */
48787a30e1fSDan Williams 	nd_device_lock(dev);
48837833fb7SDave Jiang 	nvdimm_bus_lock(dev);
48937833fb7SDave Jiang 	wait_nvdimm_bus_probe_idle(dev);
49037833fb7SDave Jiang 	rc = __security_store(dev, buf, len);
49137833fb7SDave Jiang 	nvdimm_bus_unlock(dev);
49287a30e1fSDan Williams 	nd_device_unlock(dev);
49337833fb7SDave Jiang 
49437833fb7SDave Jiang 	return rc;
49537833fb7SDave Jiang }
49637833fb7SDave Jiang static DEVICE_ATTR_RW(security);
497f2989396SDave Jiang 
49862232e45SDan Williams static struct attribute *nvdimm_attributes[] = {
499eaf96153SDan Williams 	&dev_attr_state.attr,
500efbf6f50SDan Williams 	&dev_attr_flags.attr,
50162232e45SDan Williams 	&dev_attr_commands.attr,
5020ba1c634SDan Williams 	&dev_attr_available_slots.attr,
503f2989396SDave Jiang 	&dev_attr_security.attr,
50462232e45SDan Williams 	NULL,
50562232e45SDan Williams };
50662232e45SDan Williams 
507f2989396SDave Jiang static umode_t nvdimm_visible(struct kobject *kobj, struct attribute *a, int n)
508f2989396SDave Jiang {
509f2989396SDave Jiang 	struct device *dev = container_of(kobj, typeof(*dev), kobj);
510f2989396SDave Jiang 	struct nvdimm *nvdimm = to_nvdimm(dev);
511f2989396SDave Jiang 
512f2989396SDave Jiang 	if (a != &dev_attr_security.attr)
513f2989396SDave Jiang 		return a->mode;
514f2989396SDave Jiang 	if (nvdimm->sec.state < 0)
515f2989396SDave Jiang 		return 0;
51637833fb7SDave Jiang 	/* Are there any state mutation ops? */
517d2a4ac73SDave Jiang 	if (nvdimm->sec.ops->freeze || nvdimm->sec.ops->disable
51864e77c8cSDave Jiang 			|| nvdimm->sec.ops->change_key
5197d988097SDave Jiang 			|| nvdimm->sec.ops->erase
5207d988097SDave Jiang 			|| nvdimm->sec.ops->overwrite)
521f2989396SDave Jiang 		return a->mode;
52237833fb7SDave Jiang 	return 0444;
523f2989396SDave Jiang }
524f2989396SDave Jiang 
52562232e45SDan Williams struct attribute_group nvdimm_attribute_group = {
52662232e45SDan Williams 	.attrs = nvdimm_attributes,
527f2989396SDave Jiang 	.is_visible = nvdimm_visible,
52862232e45SDan Williams };
52962232e45SDan Williams EXPORT_SYMBOL_GPL(nvdimm_attribute_group);
53062232e45SDan Williams 
531d6548ae4SDave Jiang struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
532d6548ae4SDave Jiang 		void *provider_data, const struct attribute_group **groups,
533d6548ae4SDave Jiang 		unsigned long flags, unsigned long cmd_mask, int num_flush,
534f2989396SDave Jiang 		struct resource *flush_wpq, const char *dimm_id,
535f2989396SDave Jiang 		const struct nvdimm_security_ops *sec_ops)
536e6dfb2deSDan Williams {
537e6dfb2deSDan Williams 	struct nvdimm *nvdimm = kzalloc(sizeof(*nvdimm), GFP_KERNEL);
538e6dfb2deSDan Williams 	struct device *dev;
539e6dfb2deSDan Williams 
540e6dfb2deSDan Williams 	if (!nvdimm)
541e6dfb2deSDan Williams 		return NULL;
542e6dfb2deSDan Williams 
543e6dfb2deSDan Williams 	nvdimm->id = ida_simple_get(&dimm_ida, 0, 0, GFP_KERNEL);
544e6dfb2deSDan Williams 	if (nvdimm->id < 0) {
545e6dfb2deSDan Williams 		kfree(nvdimm);
546e6dfb2deSDan Williams 		return NULL;
547e6dfb2deSDan Williams 	}
548d6548ae4SDave Jiang 
549d6548ae4SDave Jiang 	nvdimm->dimm_id = dimm_id;
550e6dfb2deSDan Williams 	nvdimm->provider_data = provider_data;
551d5d30d5aSDan Williams 	if (noblk)
552d5d30d5aSDan Williams 		flags |= 1 << NDD_NOBLK;
553e6dfb2deSDan Williams 	nvdimm->flags = flags;
554e3654ecaSDan Williams 	nvdimm->cmd_mask = cmd_mask;
555e5ae3b25SDan Williams 	nvdimm->num_flush = num_flush;
556e5ae3b25SDan Williams 	nvdimm->flush_wpq = flush_wpq;
557eaf96153SDan Williams 	atomic_set(&nvdimm->busy, 0);
558e6dfb2deSDan Williams 	dev = &nvdimm->dev;
559e6dfb2deSDan Williams 	dev_set_name(dev, "nmem%d", nvdimm->id);
560e6dfb2deSDan Williams 	dev->parent = &nvdimm_bus->dev;
561e6dfb2deSDan Williams 	dev->type = &nvdimm_device_type;
56262232e45SDan Williams 	dev->devt = MKDEV(nvdimm_major, nvdimm->id);
563e6dfb2deSDan Williams 	dev->groups = groups;
564f2989396SDave Jiang 	nvdimm->sec.ops = sec_ops;
5657d988097SDave Jiang 	nvdimm->sec.overwrite_tmo = 0;
5667d988097SDave Jiang 	INIT_DELAYED_WORK(&nvdimm->dwork, nvdimm_security_overwrite_query);
567f2989396SDave Jiang 	/*
568f2989396SDave Jiang 	 * Security state must be initialized before device_add() for
569f2989396SDave Jiang 	 * attribute visibility.
570f2989396SDave Jiang 	 */
57189fa9d8eSDave Jiang 	/* get security state and extended (master) state */
57289fa9d8eSDave Jiang 	nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
57389fa9d8eSDave Jiang 	nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
5744d88a97aSDan Williams 	nd_device_register(dev);
575e6dfb2deSDan Williams 
576e6dfb2deSDan Williams 	return nvdimm;
577e6dfb2deSDan Williams }
578d6548ae4SDave Jiang EXPORT_SYMBOL_GPL(__nvdimm_create);
5794d88a97aSDan Williams 
5801cd73865SDan Williams static void shutdown_security_notify(void *data)
5817d988097SDave Jiang {
5821cd73865SDan Williams 	struct nvdimm *nvdimm = data;
5831cd73865SDan Williams 
5841cd73865SDan Williams 	sysfs_put(nvdimm->sec.overwrite_state);
5851cd73865SDan Williams }
5861cd73865SDan Williams 
5871cd73865SDan Williams int nvdimm_security_setup_events(struct device *dev)
5881cd73865SDan Williams {
5891cd73865SDan Williams 	struct nvdimm *nvdimm = to_nvdimm(dev);
5901cd73865SDan Williams 
5911cd73865SDan Williams 	if (nvdimm->sec.state < 0 || !nvdimm->sec.ops
5921cd73865SDan Williams 			|| !nvdimm->sec.ops->overwrite)
5937d988097SDave Jiang 		return 0;
5941cd73865SDan Williams 	nvdimm->sec.overwrite_state = sysfs_get_dirent(dev->kobj.sd, "security");
5951cd73865SDan Williams 	if (!nvdimm->sec.overwrite_state)
5961cd73865SDan Williams 		return -ENOMEM;
5971cd73865SDan Williams 
5981cd73865SDan Williams 	return devm_add_action_or_reset(dev, shutdown_security_notify, nvdimm);
5997d988097SDave Jiang }
6007d988097SDave Jiang EXPORT_SYMBOL_GPL(nvdimm_security_setup_events);
6017d988097SDave Jiang 
6027d988097SDave Jiang int nvdimm_in_overwrite(struct nvdimm *nvdimm)
6037d988097SDave Jiang {
6047d988097SDave Jiang 	return test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
6057d988097SDave Jiang }
6067d988097SDave Jiang EXPORT_SYMBOL_GPL(nvdimm_in_overwrite);
6077d988097SDave Jiang 
60837833fb7SDave Jiang int nvdimm_security_freeze(struct nvdimm *nvdimm)
60937833fb7SDave Jiang {
61037833fb7SDave Jiang 	int rc;
61137833fb7SDave Jiang 
61237833fb7SDave Jiang 	WARN_ON_ONCE(!is_nvdimm_bus_locked(&nvdimm->dev));
61337833fb7SDave Jiang 
61437833fb7SDave Jiang 	if (!nvdimm->sec.ops || !nvdimm->sec.ops->freeze)
61537833fb7SDave Jiang 		return -EOPNOTSUPP;
61637833fb7SDave Jiang 
61737833fb7SDave Jiang 	if (nvdimm->sec.state < 0)
61837833fb7SDave Jiang 		return -EIO;
61937833fb7SDave Jiang 
6207d988097SDave Jiang 	if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
6217d988097SDave Jiang 		dev_warn(&nvdimm->dev, "Overwrite operation in progress.\n");
6227d988097SDave Jiang 		return -EBUSY;
6237d988097SDave Jiang 	}
6247d988097SDave Jiang 
62537833fb7SDave Jiang 	rc = nvdimm->sec.ops->freeze(nvdimm);
62689fa9d8eSDave Jiang 	nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
62737833fb7SDave Jiang 
62837833fb7SDave Jiang 	return rc;
62937833fb7SDave Jiang }
63037833fb7SDave Jiang 
631762d067dSDan Williams int alias_dpa_busy(struct device *dev, void *data)
632a1f3e4d6SDan Williams {
633fe514739SDan Williams 	resource_size_t map_end, blk_start, new;
634a1f3e4d6SDan Williams 	struct blk_alloc_info *info = data;
635a1f3e4d6SDan Williams 	struct nd_mapping *nd_mapping;
636a1f3e4d6SDan Williams 	struct nd_region *nd_region;
637a1f3e4d6SDan Williams 	struct nvdimm_drvdata *ndd;
638a1f3e4d6SDan Williams 	struct resource *res;
639a1f3e4d6SDan Williams 	int i;
640a1f3e4d6SDan Williams 
641c9e582aaSDan Williams 	if (!is_memory(dev))
642a1f3e4d6SDan Williams 		return 0;
643a1f3e4d6SDan Williams 
644a1f3e4d6SDan Williams 	nd_region = to_nd_region(dev);
645a1f3e4d6SDan Williams 	for (i = 0; i < nd_region->ndr_mappings; i++) {
646a1f3e4d6SDan Williams 		nd_mapping  = &nd_region->mapping[i];
647a1f3e4d6SDan Williams 		if (nd_mapping->nvdimm == info->nd_mapping->nvdimm)
648a1f3e4d6SDan Williams 			break;
649a1f3e4d6SDan Williams 	}
650a1f3e4d6SDan Williams 
651a1f3e4d6SDan Williams 	if (i >= nd_region->ndr_mappings)
652a1f3e4d6SDan Williams 		return 0;
653a1f3e4d6SDan Williams 
654a1f3e4d6SDan Williams 	ndd = to_ndd(nd_mapping);
655a1f3e4d6SDan Williams 	map_end = nd_mapping->start + nd_mapping->size - 1;
656a1f3e4d6SDan Williams 	blk_start = nd_mapping->start;
657762d067dSDan Williams 
658762d067dSDan Williams 	/*
659762d067dSDan Williams 	 * In the allocation case ->res is set to free space that we are
660762d067dSDan Williams 	 * looking to validate against PMEM aliasing collision rules
661762d067dSDan Williams 	 * (i.e. BLK is allocated after all aliased PMEM).
662762d067dSDan Williams 	 */
663762d067dSDan Williams 	if (info->res) {
664762d067dSDan Williams 		if (info->res->start >= nd_mapping->start
665762d067dSDan Williams 				&& info->res->start < map_end)
666762d067dSDan Williams 			/* pass */;
667762d067dSDan Williams 		else
668762d067dSDan Williams 			return 0;
669762d067dSDan Williams 	}
670762d067dSDan Williams 
671a1f3e4d6SDan Williams  retry:
672a1f3e4d6SDan Williams 	/*
673a1f3e4d6SDan Williams 	 * Find the free dpa from the end of the last pmem allocation to
674fe514739SDan Williams 	 * the end of the interleave-set mapping.
675a1f3e4d6SDan Williams 	 */
676a1f3e4d6SDan Williams 	for_each_dpa_resource(ndd, res) {
677fe514739SDan Williams 		if (strncmp(res->name, "pmem", 4) != 0)
678fe514739SDan Williams 			continue;
679a1f3e4d6SDan Williams 		if ((res->start >= blk_start && res->start < map_end)
680a1f3e4d6SDan Williams 				|| (res->end >= blk_start
681a1f3e4d6SDan Williams 					&& res->end <= map_end)) {
682fe514739SDan Williams 			new = max(blk_start, min(map_end + 1, res->end + 1));
683a1f3e4d6SDan Williams 			if (new != blk_start) {
684a1f3e4d6SDan Williams 				blk_start = new;
685a1f3e4d6SDan Williams 				goto retry;
686a1f3e4d6SDan Williams 			}
687a1f3e4d6SDan Williams 		}
688a1f3e4d6SDan Williams 	}
689a1f3e4d6SDan Williams 
690762d067dSDan Williams 	/* update the free space range with the probed blk_start */
691762d067dSDan Williams 	if (info->res && blk_start > info->res->start) {
692762d067dSDan Williams 		info->res->start = max(info->res->start, blk_start);
693762d067dSDan Williams 		if (info->res->start > info->res->end)
694762d067dSDan Williams 			info->res->end = info->res->start - 1;
695762d067dSDan Williams 		return 1;
696762d067dSDan Williams 	}
697762d067dSDan Williams 
698fe514739SDan Williams 	info->available -= blk_start - nd_mapping->start;
699762d067dSDan Williams 
700a1f3e4d6SDan Williams 	return 0;
701a1f3e4d6SDan Williams }
702a1f3e4d6SDan Williams 
703bf9bccc1SDan Williams /**
7041b40e09aSDan Williams  * nd_blk_available_dpa - account the unused dpa of BLK region
7051b40e09aSDan Williams  * @nd_mapping: container of dpa-resource-root + labels
7061b40e09aSDan Williams  *
707a1f3e4d6SDan Williams  * Unlike PMEM, BLK namespaces can occupy discontiguous DPA ranges, but
708a1f3e4d6SDan Williams  * we arrange for them to never start at an lower dpa than the last
709a1f3e4d6SDan Williams  * PMEM allocation in an aliased region.
7101b40e09aSDan Williams  */
711a1f3e4d6SDan Williams resource_size_t nd_blk_available_dpa(struct nd_region *nd_region)
7121b40e09aSDan Williams {
713a1f3e4d6SDan Williams 	struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&nd_region->dev);
714a1f3e4d6SDan Williams 	struct nd_mapping *nd_mapping = &nd_region->mapping[0];
7151b40e09aSDan Williams 	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
716a1f3e4d6SDan Williams 	struct blk_alloc_info info = {
717a1f3e4d6SDan Williams 		.nd_mapping = nd_mapping,
718a1f3e4d6SDan Williams 		.available = nd_mapping->size,
719762d067dSDan Williams 		.res = NULL,
720a1f3e4d6SDan Williams 	};
7211b40e09aSDan Williams 	struct resource *res;
7221b40e09aSDan Williams 
7231b40e09aSDan Williams 	if (!ndd)
7241b40e09aSDan Williams 		return 0;
7251b40e09aSDan Williams 
726a1f3e4d6SDan Williams 	device_for_each_child(&nvdimm_bus->dev, &info, alias_dpa_busy);
7271b40e09aSDan Williams 
728a1f3e4d6SDan Williams 	/* now account for busy blk allocations in unaliased dpa */
729a1f3e4d6SDan Williams 	for_each_dpa_resource(ndd, res) {
730a1f3e4d6SDan Williams 		if (strncmp(res->name, "blk", 3) != 0)
731a1f3e4d6SDan Williams 			continue;
732fe514739SDan Williams 		info.available -= resource_size(res);
7331b40e09aSDan Williams 	}
7341b40e09aSDan Williams 
735a1f3e4d6SDan Williams 	return info.available;
7361b40e09aSDan Williams }
7371b40e09aSDan Williams 
7381b40e09aSDan Williams /**
73912e3129eSKeith Busch  * nd_pmem_max_contiguous_dpa - For the given dimm+region, return the max
74012e3129eSKeith Busch  *			   contiguous unallocated dpa range.
74112e3129eSKeith Busch  * @nd_region: constrain available space check to this reference region
74212e3129eSKeith Busch  * @nd_mapping: container of dpa-resource-root + labels
74312e3129eSKeith Busch  */
74412e3129eSKeith Busch resource_size_t nd_pmem_max_contiguous_dpa(struct nd_region *nd_region,
74512e3129eSKeith Busch 					   struct nd_mapping *nd_mapping)
74612e3129eSKeith Busch {
74712e3129eSKeith Busch 	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
74812e3129eSKeith Busch 	struct nvdimm_bus *nvdimm_bus;
74912e3129eSKeith Busch 	resource_size_t max = 0;
75012e3129eSKeith Busch 	struct resource *res;
75112e3129eSKeith Busch 
75212e3129eSKeith Busch 	/* if a dimm is disabled the available capacity is zero */
75312e3129eSKeith Busch 	if (!ndd)
75412e3129eSKeith Busch 		return 0;
75512e3129eSKeith Busch 
75612e3129eSKeith Busch 	nvdimm_bus = walk_to_nvdimm_bus(ndd->dev);
75712e3129eSKeith Busch 	if (__reserve_free_pmem(&nd_region->dev, nd_mapping->nvdimm))
75812e3129eSKeith Busch 		return 0;
75912e3129eSKeith Busch 	for_each_dpa_resource(ndd, res) {
76012e3129eSKeith Busch 		if (strcmp(res->name, "pmem-reserve") != 0)
76112e3129eSKeith Busch 			continue;
76212e3129eSKeith Busch 		if (resource_size(res) > max)
76312e3129eSKeith Busch 			max = resource_size(res);
76412e3129eSKeith Busch 	}
76512e3129eSKeith Busch 	release_free_pmem(nvdimm_bus, nd_mapping);
76612e3129eSKeith Busch 	return max;
76712e3129eSKeith Busch }
76812e3129eSKeith Busch 
76912e3129eSKeith Busch /**
770bf9bccc1SDan Williams  * nd_pmem_available_dpa - for the given dimm+region account unallocated dpa
771bf9bccc1SDan Williams  * @nd_mapping: container of dpa-resource-root + labels
772bf9bccc1SDan Williams  * @nd_region: constrain available space check to this reference region
773bf9bccc1SDan Williams  * @overlap: calculate available space assuming this level of overlap
774bf9bccc1SDan Williams  *
775bf9bccc1SDan Williams  * Validate that a PMEM label, if present, aligns with the start of an
776bf9bccc1SDan Williams  * interleave set and truncate the available size at the lowest BLK
777bf9bccc1SDan Williams  * overlap point.
778bf9bccc1SDan Williams  *
779bf9bccc1SDan Williams  * The expectation is that this routine is called multiple times as it
780bf9bccc1SDan Williams  * probes for the largest BLK encroachment for any single member DIMM of
781bf9bccc1SDan Williams  * the interleave set.  Once that value is determined the PMEM-limit for
782bf9bccc1SDan Williams  * the set can be established.
783bf9bccc1SDan Williams  */
784bf9bccc1SDan Williams resource_size_t nd_pmem_available_dpa(struct nd_region *nd_region,
785bf9bccc1SDan Williams 		struct nd_mapping *nd_mapping, resource_size_t *overlap)
786bf9bccc1SDan Williams {
787bf9bccc1SDan Williams 	resource_size_t map_start, map_end, busy = 0, available, blk_start;
788bf9bccc1SDan Williams 	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);
789bf9bccc1SDan Williams 	struct resource *res;
790bf9bccc1SDan Williams 	const char *reason;
791bf9bccc1SDan Williams 
792bf9bccc1SDan Williams 	if (!ndd)
793bf9bccc1SDan Williams 		return 0;
794bf9bccc1SDan Williams 
795bf9bccc1SDan Williams 	map_start = nd_mapping->start;
796bf9bccc1SDan Williams 	map_end = map_start + nd_mapping->size - 1;
797bf9bccc1SDan Williams 	blk_start = max(map_start, map_end + 1 - *overlap);
798a1f3e4d6SDan Williams 	for_each_dpa_resource(ndd, res) {
799bf9bccc1SDan Williams 		if (res->start >= map_start && res->start < map_end) {
800bf9bccc1SDan Williams 			if (strncmp(res->name, "blk", 3) == 0)
801a1f3e4d6SDan Williams 				blk_start = min(blk_start,
802a1f3e4d6SDan Williams 						max(map_start, res->start));
803a1f3e4d6SDan Williams 			else if (res->end > map_end) {
804bf9bccc1SDan Williams 				reason = "misaligned to iset";
805bf9bccc1SDan Williams 				goto err;
806a1f3e4d6SDan Williams 			} else
807bf9bccc1SDan Williams 				busy += resource_size(res);
808bf9bccc1SDan Williams 		} else if (res->end >= map_start && res->end <= map_end) {
809bf9bccc1SDan Williams 			if (strncmp(res->name, "blk", 3) == 0) {
810bf9bccc1SDan Williams 				/*
811bf9bccc1SDan Williams 				 * If a BLK allocation overlaps the start of
812bf9bccc1SDan Williams 				 * PMEM the entire interleave set may now only
813bf9bccc1SDan Williams 				 * be used for BLK.
814bf9bccc1SDan Williams 				 */
815bf9bccc1SDan Williams 				blk_start = map_start;
816a1f3e4d6SDan Williams 			} else
817a1f3e4d6SDan Williams 				busy += resource_size(res);
818bf9bccc1SDan Williams 		} else if (map_start > res->start && map_start < res->end) {
819bf9bccc1SDan Williams 			/* total eclipse of the mapping */
820bf9bccc1SDan Williams 			busy += nd_mapping->size;
821bf9bccc1SDan Williams 			blk_start = map_start;
822bf9bccc1SDan Williams 		}
823a1f3e4d6SDan Williams 	}
824bf9bccc1SDan Williams 
825bf9bccc1SDan Williams 	*overlap = map_end + 1 - blk_start;
826bf9bccc1SDan Williams 	available = blk_start - map_start;
827bf9bccc1SDan Williams 	if (busy < available)
828bf9bccc1SDan Williams 		return available - busy;
829bf9bccc1SDan Williams 	return 0;
830bf9bccc1SDan Williams 
831bf9bccc1SDan Williams  err:
832bf9bccc1SDan Williams 	nd_dbg_dpa(nd_region, ndd, res, "%s\n", reason);
833bf9bccc1SDan Williams 	return 0;
834bf9bccc1SDan Williams }
835bf9bccc1SDan Williams 
8364a826c83SDan Williams void nvdimm_free_dpa(struct nvdimm_drvdata *ndd, struct resource *res)
8374a826c83SDan Williams {
8384a826c83SDan Williams 	WARN_ON_ONCE(!is_nvdimm_bus_locked(ndd->dev));
8394a826c83SDan Williams 	kfree(res->name);
8404a826c83SDan Williams 	__release_region(&ndd->dpa, res->start, resource_size(res));
8414a826c83SDan Williams }
8424a826c83SDan Williams 
8434a826c83SDan Williams struct resource *nvdimm_allocate_dpa(struct nvdimm_drvdata *ndd,
8444a826c83SDan Williams 		struct nd_label_id *label_id, resource_size_t start,
8454a826c83SDan Williams 		resource_size_t n)
8464a826c83SDan Williams {
8474a826c83SDan Williams 	char *name = kmemdup(label_id, sizeof(*label_id), GFP_KERNEL);
8484a826c83SDan Williams 	struct resource *res;
8494a826c83SDan Williams 
8504a826c83SDan Williams 	if (!name)
8514a826c83SDan Williams 		return NULL;
8524a826c83SDan Williams 
8534a826c83SDan Williams 	WARN_ON_ONCE(!is_nvdimm_bus_locked(ndd->dev));
8544a826c83SDan Williams 	res = __request_region(&ndd->dpa, start, n, name, 0);
8554a826c83SDan Williams 	if (!res)
8564a826c83SDan Williams 		kfree(name);
8574a826c83SDan Williams 	return res;
8584a826c83SDan Williams }
8594a826c83SDan Williams 
860bf9bccc1SDan Williams /**
861bf9bccc1SDan Williams  * nvdimm_allocated_dpa - sum up the dpa currently allocated to this label_id
862bf9bccc1SDan Williams  * @nvdimm: container of dpa-resource-root + labels
863bf9bccc1SDan Williams  * @label_id: dpa resource name of the form {pmem|blk}-<human readable uuid>
864bf9bccc1SDan Williams  */
865bf9bccc1SDan Williams resource_size_t nvdimm_allocated_dpa(struct nvdimm_drvdata *ndd,
866bf9bccc1SDan Williams 		struct nd_label_id *label_id)
867bf9bccc1SDan Williams {
868bf9bccc1SDan Williams 	resource_size_t allocated = 0;
869bf9bccc1SDan Williams 	struct resource *res;
870bf9bccc1SDan Williams 
871bf9bccc1SDan Williams 	for_each_dpa_resource(ndd, res)
872bf9bccc1SDan Williams 		if (strcmp(res->name, label_id->id) == 0)
873bf9bccc1SDan Williams 			allocated += resource_size(res);
874bf9bccc1SDan Williams 
875bf9bccc1SDan Williams 	return allocated;
876bf9bccc1SDan Williams }
877bf9bccc1SDan Williams 
8784d88a97aSDan Williams static int count_dimms(struct device *dev, void *c)
8794d88a97aSDan Williams {
8804d88a97aSDan Williams 	int *count = c;
8814d88a97aSDan Williams 
8824d88a97aSDan Williams 	if (is_nvdimm(dev))
8834d88a97aSDan Williams 		(*count)++;
8844d88a97aSDan Williams 	return 0;
8854d88a97aSDan Williams }
8864d88a97aSDan Williams 
8874d88a97aSDan Williams int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count)
8884d88a97aSDan Williams {
8894d88a97aSDan Williams 	int count = 0;
8904d88a97aSDan Williams 	/* Flush any possible dimm registration failures */
8914d88a97aSDan Williams 	nd_synchronize();
8924d88a97aSDan Williams 
8934d88a97aSDan Williams 	device_for_each_child(&nvdimm_bus->dev, &count, count_dimms);
894426824d6SDan Williams 	dev_dbg(&nvdimm_bus->dev, "count: %d\n", count);
8954d88a97aSDan Williams 	if (count != dimm_count)
8964d88a97aSDan Williams 		return -ENXIO;
8974d88a97aSDan Williams 	return 0;
8984d88a97aSDan Williams }
8994d88a97aSDan Williams EXPORT_SYMBOL_GPL(nvdimm_bus_check_dimm_count);
900b354aba0SDan Williams 
901b354aba0SDan Williams void __exit nvdimm_devs_exit(void)
902b354aba0SDan Williams {
903b354aba0SDan Williams 	ida_destroy(&dimm_ida);
904b354aba0SDan Williams }
905