2231 lines · 55.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. */#include <linux/kstrtox.h>#include <linux/module.h>#include <linux/device.h>#include <linux/sort.h>#include <linux/slab.h>#include <linux/list.h>#include <linux/nd.h>#include "nd-core.h"#include "pmem.h"#include "pfn.h"#include "nd.h" static void namespace_io_release(struct device *dev){	struct nd_namespace_io *nsio = to_nd_namespace_io(dev); 	kfree(nsio);} static void namespace_pmem_release(struct device *dev){	struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev);	struct nd_region *nd_region = to_nd_region(dev->parent); 	if (nspm->id >= 0)		ida_free(&nd_region->ns_ida, nspm->id);	kfree(nspm->alt_name);	kfree(nspm->uuid);	kfree(nspm);} static bool is_namespace_pmem(const struct device *dev);static bool is_namespace_io(const struct device *dev); static int is_uuid_busy(struct device *dev, void *data){	uuid_t *uuid1 = data, *uuid2 = NULL; 	if (is_namespace_pmem(dev)) {		struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev); 		uuid2 = nspm->uuid;	} else if (is_nd_btt(dev)) {		struct nd_btt *nd_btt = to_nd_btt(dev); 		uuid2 = nd_btt->uuid;	} else if (is_nd_pfn(dev)) {		struct nd_pfn *nd_pfn = to_nd_pfn(dev); 		uuid2 = nd_pfn->uuid;	} 	if (uuid2 && uuid_equal(uuid1, uuid2))		return -EBUSY; 	return 0;} static int is_namespace_uuid_busy(struct device *dev, void *data){	if (is_nd_region(dev))		return device_for_each_child(dev, data, is_uuid_busy);	return 0;} /** * nd_is_uuid_unique - verify that no other namespace has @uuid * @dev: any device on a nvdimm_bus * @uuid: uuid to check * * Returns: %true if the uuid is unique, %false if not */bool nd_is_uuid_unique(struct device *dev, uuid_t *uuid){	struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); 	if (!nvdimm_bus)		return false;	WARN_ON_ONCE(!is_nvdimm_bus_locked(&nvdimm_bus->dev));	if (device_for_each_child(&nvdimm_bus->dev, uuid,				is_namespace_uuid_busy) != 0)		return false;	return true;} bool pmem_should_map_pages(struct device *dev){	struct nd_region *nd_region = to_nd_region(dev->parent);	struct nd_namespace_common *ndns = to_ndns(dev);	struct nd_namespace_io *nsio; 	if (!IS_ENABLED(CONFIG_ZONE_DEVICE))		return false; 	if (!test_bit(ND_REGION_PAGEMAP, &nd_region->flags))		return false; 	if (is_nd_pfn(dev) || is_nd_btt(dev))		return false; 	if (ndns->force_raw)		return false; 	nsio = to_nd_namespace_io(dev);	if (region_intersects(nsio->res.start, resource_size(&nsio->res),				IORESOURCE_SYSTEM_RAM,				IORES_DESC_NONE) == REGION_MIXED)		return false; 	return ARCH_MEMREMAP_PMEM == MEMREMAP_WB;}EXPORT_SYMBOL(pmem_should_map_pages); unsigned int pmem_sector_size(struct nd_namespace_common *ndns){	if (is_namespace_pmem(&ndns->dev)) {		struct nd_namespace_pmem *nspm; 		nspm = to_nd_namespace_pmem(&ndns->dev);		if (nspm->lbasize == 0 || nspm->lbasize == 512)			/* default */;		else if (nspm->lbasize == 4096)			return 4096;		else			dev_WARN(&ndns->dev, "unsupported sector size: %ld\n",					nspm->lbasize);	} 	/*	 * There is no namespace label (is_namespace_io()), or the label	 * indicates the default sector size.	 */	return 512;}EXPORT_SYMBOL(pmem_sector_size); const char *nvdimm_namespace_disk_name(struct nd_namespace_common *ndns,		char *name){	struct nd_region *nd_region = to_nd_region(ndns->dev.parent);	const char *suffix = NULL; 	if (ndns->claim && is_nd_btt(ndns->claim))		suffix = "s"; 	if (is_namespace_pmem(&ndns->dev) || is_namespace_io(&ndns->dev)) {		int nsidx = 0; 		if (is_namespace_pmem(&ndns->dev)) {			struct nd_namespace_pmem *nspm; 			nspm = to_nd_namespace_pmem(&ndns->dev);			nsidx = nspm->id;		} 		if (nsidx)			sprintf(name, "pmem%d.%d%s", nd_region->id, nsidx,					suffix ? suffix : "");		else			sprintf(name, "pmem%d%s", nd_region->id,					suffix ? suffix : "");	} else {		return NULL;	} 	return name;}EXPORT_SYMBOL(nvdimm_namespace_disk_name); const uuid_t *nd_dev_to_uuid(struct device *dev){	if (dev && is_namespace_pmem(dev)) {		struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev); 		return nspm->uuid;	}	return &uuid_null;}EXPORT_SYMBOL(nd_dev_to_uuid); static ssize_t nstype_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct nd_region *nd_region = to_nd_region(dev->parent); 	return sprintf(buf, "%d\n", nd_region_to_nstype(nd_region));}static DEVICE_ATTR_RO(nstype); static ssize_t __alt_name_store(struct device *dev, const char *buf,		const size_t len){	char *input, *pos, *alt_name, **ns_altname;	ssize_t rc; 	if (is_namespace_pmem(dev)) {		struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev); 		ns_altname = &nspm->alt_name;	} else		return -ENXIO; 	if (dev->driver || to_ndns(dev)->claim)		return -EBUSY; 	input = kstrndup(buf, len, GFP_KERNEL);	if (!input)		return -ENOMEM; 	pos = strim(input);	if (strlen(pos) + 1 > NSLABEL_NAME_LEN) {		rc = -EINVAL;		goto out;	} 	alt_name = kzalloc(NSLABEL_NAME_LEN, GFP_KERNEL);	if (!alt_name) {		rc = -ENOMEM;		goto out;	}	kfree(*ns_altname);	*ns_altname = alt_name;	sprintf(*ns_altname, "%s", pos);	rc = len; out:	kfree(input);	return rc;} static int nd_namespace_label_update(struct nd_region *nd_region,		struct device *dev){	dev_WARN_ONCE(dev, dev->driver || to_ndns(dev)->claim,			"namespace must be idle during label update\n");	if (dev->driver || to_ndns(dev)->claim)		return 0; 	/*	 * Only allow label writes that will result in a valid namespace	 * or deletion of an existing namespace.	 */	if (is_namespace_pmem(dev)) {		struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev);		resource_size_t size = resource_size(&nspm->nsio.res); 		if (size == 0 && nspm->uuid)			/* delete allocation */;		else if (!nspm->uuid)			return 0; 		return nd_pmem_namespace_label_update(nd_region, nspm, size);	} else		return -ENXIO;} static ssize_t alt_name_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t len){	struct nd_region *nd_region = to_nd_region(dev->parent);	ssize_t rc; 	guard(device)(dev);	guard(nvdimm_bus)(dev);	wait_nvdimm_bus_probe_idle(dev);	rc = __alt_name_store(dev, buf, len);	if (rc >= 0)		rc = nd_namespace_label_update(nd_region, dev);	dev_dbg(dev, "%s(%zd)\n", rc < 0 ? "fail " : "", rc); 	return rc < 0 ? rc : len;} static ssize_t alt_name_show(struct device *dev,		struct device_attribute *attr, char *buf){	char *ns_altname; 	if (is_namespace_pmem(dev)) {		struct nd_namespace_pmem *nspm = to_nd_namespace_pmem(dev); 		ns_altname = nspm->alt_name;	} else		return -ENXIO; 	return sprintf(buf, "%s\n", ns_altname ? ns_altname : "");}static DEVICE_ATTR_RW(alt_name); static int scan_free(struct nd_region *nd_region,		struct nd_mapping *nd_mapping, struct nd_label_id *label_id,		resource_size_t n){	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);	int rc = 0; 	while (n) {		struct resource *res, *last; 		last = NULL;		for_each_dpa_resource(ndd, res)			if (strcmp(res->name, label_id->id) == 0)				last = res;		res = last;		if (!res)			return 0; 		if (n >= resource_size(res)) {			n -= resource_size(res);			nd_dbg_dpa(nd_region, ndd, res, "delete %d\n", rc);			nvdimm_free_dpa(ndd, res);			/* retry with last resource deleted */			continue;		} 		rc = adjust_resource(res, res->start, resource_size(res) - n);		if (rc == 0)			res->flags |= DPA_RESOURCE_ADJUSTED;		nd_dbg_dpa(nd_region, ndd, res, "shrink %d\n", rc);		break;	} 	return rc;} /** * shrink_dpa_allocation - for each dimm in region free n bytes for label_id * @nd_region: the set of dimms to reclaim @n bytes from * @label_id: unique identifier for the namespace consuming this dpa range * @n: number of bytes per-dimm to release * * Assumes resources are ordered.  Starting from the end try to * adjust_resource() the allocation to @n, but if @n is larger than the * allocation delete it and find the 'new' last allocation in the label * set. * * Returns: %0 on success on -errno on error */static int shrink_dpa_allocation(struct nd_region *nd_region,		struct nd_label_id *label_id, resource_size_t n){	int i; 	for (i = 0; i < nd_region->ndr_mappings; i++) {		struct nd_mapping *nd_mapping = &nd_region->mapping[i];		int rc; 		rc = scan_free(nd_region, nd_mapping, label_id, n);		if (rc)			return rc;	} 	return 0;} static resource_size_t init_dpa_allocation(struct nd_label_id *label_id,		struct nd_region *nd_region, struct nd_mapping *nd_mapping,		resource_size_t n){	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);	struct resource *res;	int rc = 0; 	/* first resource allocation for this label-id or dimm */	res = nvdimm_allocate_dpa(ndd, label_id, nd_mapping->start, n);	if (!res)		rc = -EBUSY; 	nd_dbg_dpa(nd_region, ndd, res, "init %d\n", rc);	return rc ? n : 0;}  /** * space_valid() - validate free dpa space against constraints * @nd_region: hosting region of the free space * @ndd: dimm device data for debug * @label_id: namespace id to allocate space * @prev: potential allocation that precedes free space * @next: allocation that follows the given free space range * @exist: first allocation with same id in the mapping * @n: range that must satisfied for pmem allocations * @valid: free space range to validate * * BLK-space is valid as long as it does not precede a PMEM * allocation in a given region. PMEM-space must be contiguous * and adjacent to an existing allocation (if one * exists).  If reserving PMEM any space is valid. */static void space_valid(struct nd_region *nd_region, struct nvdimm_drvdata *ndd,		struct nd_label_id *label_id, struct resource *prev,		struct resource *next, struct resource *exist,		resource_size_t n, struct resource *valid){	bool is_reserve = strcmp(label_id->id, "pmem-reserve") == 0;	unsigned long align; 	align = nd_region->align / nd_region->ndr_mappings;	valid->start = ALIGN(valid->start, align);	valid->end = ALIGN_DOWN(valid->end + 1, align) - 1; 	if (valid->start >= valid->end)		goto invalid; 	if (is_reserve)		return; 	/* allocation needs to be contiguous, so this is all or nothing */	if (resource_size(valid) < n)		goto invalid; 	/* we've got all the space we need and no existing allocation */	if (!exist)		return; 	/* allocation needs to be contiguous with the existing namespace */	if (valid->start == exist->end + 1			|| valid->end == exist->start - 1)		return;  invalid:	/* truncate @valid size to 0 */	valid->end = valid->start - 1;} enum alloc_loc {	ALLOC_ERR = 0, ALLOC_BEFORE, ALLOC_MID, ALLOC_AFTER,}; static resource_size_t scan_allocate(struct nd_region *nd_region,		struct nd_mapping *nd_mapping, struct nd_label_id *label_id,		resource_size_t n){	resource_size_t mapping_end = nd_mapping->start + nd_mapping->size - 1;	struct nvdimm_drvdata *ndd = to_ndd(nd_mapping);	struct resource *res, *exist = NULL, valid;	const resource_size_t to_allocate = n;	int first; 	for_each_dpa_resource(ndd, res)		if (strcmp(label_id->id, res->name) == 0)			exist = res; 	valid.start = nd_mapping->start;	valid.end = mapping_end;	valid.name = "free space"; retry:	first = 0;	for_each_dpa_resource(ndd, res) {		struct resource *next = res->sibling, *new_res = NULL;		resource_size_t allocate, available = 0;		enum alloc_loc loc = ALLOC_ERR;		const char *action;		int rc = 0; 		/* ignore resources outside this nd_mapping */		if (res->start > mapping_end)			continue;		if (res->end < nd_mapping->start)			continue; 		/* space at the beginning of the mapping */		if (!first++ && res->start > nd_mapping->start) {			valid.start = nd_mapping->start;			valid.end = res->start - 1;			space_valid(nd_region, ndd, label_id, NULL, next, exist,					to_allocate, &valid);			available = resource_size(&valid);			if (available)				loc = ALLOC_BEFORE;		} 		/* space between allocations */		if (!loc && next) {			valid.start = res->start + resource_size(res);			valid.end = min(mapping_end, next->start - 1);			space_valid(nd_region, ndd, label_id, res, next, exist,					to_allocate, &valid);			available = resource_size(&valid);			if (available)				loc = ALLOC_MID;		} 		/* space at the end of the mapping */		if (!loc && !next) {			valid.start = res->start + resource_size(res);			valid.end = mapping_end;			space_valid(nd_region, ndd, label_id, res, next, exist,					to_allocate, &valid);			available = resource_size(&valid);			if (available)				loc = ALLOC_AFTER;		} 		if (!loc || !available)			continue;

Lines 1–500 of 2231.