Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 178d6f4b authored by Dan Williams's avatar Dan Williams
Browse files

Merge branch 'for-4.9/libnvdimm' into libnvdimm-for-next

parents db58028e 98a29c39
Loading
Loading
Loading
Loading
+180 −30
Original line number Diff line number Diff line
@@ -886,6 +886,58 @@ static ssize_t revision_show(struct device *dev,
}
static DEVICE_ATTR_RO(revision);

static ssize_t hw_error_scrub_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
	struct nvdimm_bus_descriptor *nd_desc = to_nd_desc(nvdimm_bus);
	struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);

	return sprintf(buf, "%d\n", acpi_desc->scrub_mode);
}

/*
 * The 'hw_error_scrub' attribute can have the following values written to it:
 * '0': Switch to the default mode where an exception will only insert
 *      the address of the memory error into the poison and badblocks lists.
 * '1': Enable a full scrub to happen if an exception for a memory error is
 *      received.
 */
static ssize_t hw_error_scrub_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct nvdimm_bus_descriptor *nd_desc;
	ssize_t rc;
	long val;

	rc = kstrtol(buf, 0, &val);
	if (rc)
		return rc;

	device_lock(dev);
	nd_desc = dev_get_drvdata(dev);
	if (nd_desc) {
		struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);

		switch (val) {
		case HW_ERROR_SCRUB_ON:
			acpi_desc->scrub_mode = HW_ERROR_SCRUB_ON;
			break;
		case HW_ERROR_SCRUB_OFF:
			acpi_desc->scrub_mode = HW_ERROR_SCRUB_OFF;
			break;
		default:
			rc = -EINVAL;
			break;
		}
	}
	device_unlock(dev);
	if (rc)
		return rc;
	return size;
}
static DEVICE_ATTR_RW(hw_error_scrub);

/*
 * This shows the number of full Address Range Scrubs that have been
 * completed since driver load time. Userspace can wait on this using
@@ -958,6 +1010,7 @@ static umode_t nfit_visible(struct kobject *kobj, struct attribute *a, int n)
static struct attribute *acpi_nfit_attributes[] = {
	&dev_attr_revision.attr,
	&dev_attr_scrub.attr,
	&dev_attr_hw_error_scrub.attr,
	NULL,
};

@@ -1256,6 +1309,44 @@ static struct nvdimm *acpi_nfit_dimm_by_handle(struct acpi_nfit_desc *acpi_desc,
	return NULL;
}

void __acpi_nvdimm_notify(struct device *dev, u32 event)
{
	struct nfit_mem *nfit_mem;
	struct acpi_nfit_desc *acpi_desc;

	dev_dbg(dev->parent, "%s: %s: event: %d\n", dev_name(dev), __func__,
			event);

	if (event != NFIT_NOTIFY_DIMM_HEALTH) {
		dev_dbg(dev->parent, "%s: unknown event: %d\n", dev_name(dev),
				event);
		return;
	}

	acpi_desc = dev_get_drvdata(dev->parent);
	if (!acpi_desc)
		return;

	/*
	 * If we successfully retrieved acpi_desc, then we know nfit_mem data
	 * is still valid.
	 */
	nfit_mem = dev_get_drvdata(dev);
	if (nfit_mem && nfit_mem->flags_attr)
		sysfs_notify_dirent(nfit_mem->flags_attr);
}
EXPORT_SYMBOL_GPL(__acpi_nvdimm_notify);

static void acpi_nvdimm_notify(acpi_handle handle, u32 event, void *data)
{
	struct acpi_device *adev = data;
	struct device *dev = &adev->dev;

	device_lock(dev->parent);
	__acpi_nvdimm_notify(dev, event);
	device_unlock(dev->parent);
}

static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
		struct nfit_mem *nfit_mem, u32 device_handle)
{
@@ -1280,6 +1371,13 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
		return force_enable_dimms ? 0 : -ENODEV;
	}

	if (ACPI_FAILURE(acpi_install_notify_handler(adev_dimm->handle,
		ACPI_DEVICE_NOTIFY, acpi_nvdimm_notify, adev_dimm))) {
		dev_err(dev, "%s: notification registration failed\n",
				dev_name(&adev_dimm->dev));
		return -ENXIO;
	}

	/*
	 * Until standardization materializes we need to consider 4
	 * different command sets.  Note, that checking for function0 (bit0)
@@ -1318,18 +1416,41 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
	return 0;
}

static void shutdown_dimm_notify(void *data)
{
	struct acpi_nfit_desc *acpi_desc = data;
	struct nfit_mem *nfit_mem;

	mutex_lock(&acpi_desc->init_mutex);
	/*
	 * Clear out the nfit_mem->flags_attr and shut down dimm event
	 * notifications.
	 */
	list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
		struct acpi_device *adev_dimm = nfit_mem->adev;

		if (nfit_mem->flags_attr) {
			sysfs_put(nfit_mem->flags_attr);
			nfit_mem->flags_attr = NULL;
		}
		if (adev_dimm)
			acpi_remove_notify_handler(adev_dimm->handle,
					ACPI_DEVICE_NOTIFY, acpi_nvdimm_notify);
	}
	mutex_unlock(&acpi_desc->init_mutex);
}

static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
{
	struct nfit_mem *nfit_mem;
	int dimm_count = 0;
	int dimm_count = 0, rc;
	struct nvdimm *nvdimm;

	list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
		struct acpi_nfit_flush_address *flush;
		unsigned long flags = 0, cmd_mask;
		struct nvdimm *nvdimm;
		u32 device_handle;
		u16 mem_flags;
		int rc;

		device_handle = __to_nfit_memdev(nfit_mem)->device_handle;
		nvdimm = acpi_nfit_dimm_by_handle(acpi_desc, device_handle);
@@ -1382,7 +1503,30 @@ static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)

	}

	return nvdimm_bus_check_dimm_count(acpi_desc->nvdimm_bus, dimm_count);
	rc = nvdimm_bus_check_dimm_count(acpi_desc->nvdimm_bus, dimm_count);
	if (rc)
		return rc;

	/*
	 * Now that dimms are successfully registered, and async registration
	 * is flushed, attempt to enable event notification.
	 */
	list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) {
		struct kernfs_node *nfit_kernfs;

		nvdimm = nfit_mem->nvdimm;
		nfit_kernfs = sysfs_get_dirent(nvdimm_kobj(nvdimm)->sd, "nfit");
		if (nfit_kernfs)
			nfit_mem->flags_attr = sysfs_get_dirent(nfit_kernfs,
					"flags");
		sysfs_put(nfit_kernfs);
		if (!nfit_mem->flags_attr)
			dev_warn(acpi_desc->dev, "%s: notifications disabled\n",
					nvdimm_name(nvdimm));
	}

	return devm_add_action_or_reset(acpi_desc->dev, shutdown_dimm_notify,
			acpi_desc);
}

static void acpi_nfit_init_dsms(struct acpi_nfit_desc *acpi_desc)
@@ -1491,9 +1635,9 @@ static int acpi_nfit_init_interleave_set(struct acpi_nfit_desc *acpi_desc,
	if (!info)
		return -ENOMEM;
	for (i = 0; i < nr; i++) {
		struct nd_mapping *nd_mapping = &ndr_desc->nd_mapping[i];
		struct nd_mapping_desc *mapping = &ndr_desc->mapping[i];
		struct nfit_set_info_map *map = &info->mapping[i];
		struct nvdimm *nvdimm = nd_mapping->nvdimm;
		struct nvdimm *nvdimm = mapping->nvdimm;
		struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
		struct acpi_nfit_memory_map *memdev = memdev_from_spa(acpi_desc,
				spa->range_index, i);
@@ -1917,7 +2061,7 @@ static int acpi_nfit_insert_resource(struct acpi_nfit_desc *acpi_desc,
}

static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
		struct nd_mapping *nd_mapping, struct nd_region_desc *ndr_desc,
		struct nd_mapping_desc *mapping, struct nd_region_desc *ndr_desc,
		struct acpi_nfit_memory_map *memdev,
		struct nfit_spa *nfit_spa)
{
@@ -1934,12 +2078,12 @@ static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
		return -ENODEV;
	}

	nd_mapping->nvdimm = nvdimm;
	mapping->nvdimm = nvdimm;
	switch (nfit_spa_type(spa)) {
	case NFIT_SPA_PM:
	case NFIT_SPA_VOLATILE:
		nd_mapping->start = memdev->address;
		nd_mapping->size = memdev->region_size;
		mapping->start = memdev->address;
		mapping->size = memdev->region_size;
		break;
	case NFIT_SPA_DCR:
		nfit_mem = nvdimm_provider_data(nvdimm);
@@ -1947,13 +2091,13 @@ static int acpi_nfit_init_mapping(struct acpi_nfit_desc *acpi_desc,
			dev_dbg(acpi_desc->dev, "spa%d %s missing bdw\n",
					spa->range_index, nvdimm_name(nvdimm));
		} else {
			nd_mapping->size = nfit_mem->bdw->capacity;
			nd_mapping->start = nfit_mem->bdw->start_address;
			mapping->size = nfit_mem->bdw->capacity;
			mapping->start = nfit_mem->bdw->start_address;
			ndr_desc->num_lanes = nfit_mem->bdw->windows;
			blk_valid = 1;
		}

		ndr_desc->nd_mapping = nd_mapping;
		ndr_desc->mapping = mapping;
		ndr_desc->num_mappings = blk_valid;
		ndbr_desc = to_blk_region_desc(ndr_desc);
		ndbr_desc->enable = acpi_nfit_blk_region_enable;
@@ -1979,7 +2123,7 @@ static bool nfit_spa_is_virtual(struct acpi_nfit_system_address *spa)
static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
		struct nfit_spa *nfit_spa)
{
	static struct nd_mapping nd_mappings[ND_MAX_MAPPINGS];
	static struct nd_mapping_desc mappings[ND_MAX_MAPPINGS];
	struct acpi_nfit_system_address *spa = nfit_spa->spa;
	struct nd_blk_region_desc ndbr_desc;
	struct nd_region_desc *ndr_desc;
@@ -1998,7 +2142,7 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
	}

	memset(&res, 0, sizeof(res));
	memset(&nd_mappings, 0, sizeof(nd_mappings));
	memset(&mappings, 0, sizeof(mappings));
	memset(&ndbr_desc, 0, sizeof(ndbr_desc));
	res.start = spa->address;
	res.end = res.start + spa->length - 1;
@@ -2014,7 +2158,7 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,

	list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) {
		struct acpi_nfit_memory_map *memdev = nfit_memdev->memdev;
		struct nd_mapping *nd_mapping;
		struct nd_mapping_desc *mapping;

		if (memdev->range_index != spa->range_index)
			continue;
@@ -2023,14 +2167,14 @@ static int acpi_nfit_register_region(struct acpi_nfit_desc *acpi_desc,
					spa->range_index, ND_MAX_MAPPINGS);
			return -ENXIO;
		}
		nd_mapping = &nd_mappings[count++];
		rc = acpi_nfit_init_mapping(acpi_desc, nd_mapping, ndr_desc,
		mapping = &mappings[count++];
		rc = acpi_nfit_init_mapping(acpi_desc, mapping, ndr_desc,
				memdev, nfit_spa);
		if (rc)
			goto out;
	}

	ndr_desc->nd_mapping = nd_mappings;
	ndr_desc->mapping = mappings;
	ndr_desc->num_mappings = count;
	rc = acpi_nfit_init_interleave_set(acpi_desc, ndr_desc, spa);
	if (rc)
@@ -2678,29 +2822,30 @@ static int acpi_nfit_remove(struct acpi_device *adev)
	return 0;
}

static void acpi_nfit_notify(struct acpi_device *adev, u32 event)
void __acpi_nfit_notify(struct device *dev, acpi_handle handle, u32 event)
{
	struct acpi_nfit_desc *acpi_desc = dev_get_drvdata(&adev->dev);
	struct acpi_nfit_desc *acpi_desc = dev_get_drvdata(dev);
	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
	struct device *dev = &adev->dev;
	union acpi_object *obj;
	acpi_status status;
	int ret;

	dev_dbg(dev, "%s: event: %d\n", __func__, event);

	device_lock(dev);
	if (event != NFIT_NOTIFY_UPDATE)
		return;

	if (!dev->driver) {
		/* dev->driver may be null if we're being removed */
		dev_dbg(dev, "%s: no driver found for dev\n", __func__);
		goto out_unlock;
		return;
	}

	if (!acpi_desc) {
		acpi_desc = devm_kzalloc(dev, sizeof(*acpi_desc), GFP_KERNEL);
		if (!acpi_desc)
			goto out_unlock;
		acpi_nfit_desc_init(acpi_desc, &adev->dev);
			return;
		acpi_nfit_desc_init(acpi_desc, dev);
	} else {
		/*
		 * Finish previous registration before considering new
@@ -2710,10 +2855,10 @@ static void acpi_nfit_notify(struct acpi_device *adev, u32 event)
	}

	/* Evaluate _FIT */
	status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf);
	status = acpi_evaluate_object(handle, "_FIT", NULL, &buf);
	if (ACPI_FAILURE(status)) {
		dev_err(dev, "failed to evaluate _FIT\n");
		goto out_unlock;
		return;
	}

	obj = buf.pointer;
@@ -2725,9 +2870,14 @@ static void acpi_nfit_notify(struct acpi_device *adev, u32 event)
	} else
		dev_err(dev, "Invalid _FIT\n");
	kfree(buf.pointer);
}
EXPORT_SYMBOL_GPL(__acpi_nfit_notify);

 out_unlock:
	device_unlock(dev);
static void acpi_nfit_notify(struct acpi_device *adev, u32 event)
{
	device_lock(&adev->dev);
	__acpi_nfit_notify(&adev->dev, adev->handle, event);
	device_unlock(&adev->dev);
}

static const struct acpi_device_id acpi_nfit_ids[] = {
+19 −5
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
 */
#include <linux/notifier.h>
#include <linux/acpi.h>
#include <linux/nd.h>
#include <asm/mce.h>
#include "nfit.h"

@@ -62,13 +63,26 @@ static int nfit_handle_mce(struct notifier_block *nb, unsigned long val,
		}
		mutex_unlock(&acpi_desc->init_mutex);

		if (!found_match)
			continue;

		/* If this fails due to an -ENOMEM, there is little we can do */
		nvdimm_bus_add_poison(acpi_desc->nvdimm_bus,
				ALIGN(mce->addr, L1_CACHE_BYTES),
				L1_CACHE_BYTES);
		nvdimm_region_notify(nfit_spa->nd_region,
				NVDIMM_REVALIDATE_POISON);

		if (acpi_desc->scrub_mode == HW_ERROR_SCRUB_ON) {
			/*
		 * We can ignore an -EBUSY here because if an ARS is already
		 * in progress, just let that be the last authoritative one
			 * We can ignore an -EBUSY here because if an ARS is
			 * already in progress, just let that be the last
			 * authoritative one
			 */
		if (found_match)
			acpi_nfit_ars_rescan(acpi_desc);
		}
		break;
	}

	mutex_unlock(&acpi_desc_lock);
	return NOTIFY_DONE;
+17 −0
Original line number Diff line number Diff line
@@ -78,6 +78,14 @@ enum {
	NFIT_ARS_TIMEOUT = 90,
};

enum nfit_root_notifiers {
	NFIT_NOTIFY_UPDATE = 0x80,
};

enum nfit_dimm_notifiers {
	NFIT_NOTIFY_DIMM_HEALTH = 0x81,
};

struct nfit_spa {
	struct list_head list;
	struct nd_region *nd_region;
@@ -124,6 +132,7 @@ struct nfit_mem {
	struct acpi_nfit_system_address *spa_bdw;
	struct acpi_nfit_interleave *idt_dcr;
	struct acpi_nfit_interleave *idt_bdw;
	struct kernfs_node *flags_attr;
	struct nfit_flush *nfit_flush;
	struct list_head list;
	struct acpi_device *adev;
@@ -152,6 +161,7 @@ struct acpi_nfit_desc {
	struct list_head list;
	struct kernfs_node *scrub_count_state;
	unsigned int scrub_count;
	unsigned int scrub_mode;
	unsigned int cancel:1;
	unsigned long dimm_cmd_force_en;
	unsigned long bus_cmd_force_en;
@@ -159,6 +169,11 @@ struct acpi_nfit_desc {
			void *iobuf, u64 len, int rw);
};

enum scrub_mode {
	HW_ERROR_SCRUB_OFF,
	HW_ERROR_SCRUB_ON,
};

enum nd_blk_mmio_selector {
	BDW,
	DCR,
@@ -223,5 +238,7 @@ static inline struct acpi_nfit_desc *to_acpi_desc(

const u8 *to_nfit_uuid(enum nfit_uuids id);
int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *nfit, acpi_size sz);
void __acpi_nfit_notify(struct device *dev, acpi_handle handle, u32 event);
void __acpi_nvdimm_notify(struct device *dev, u32 event);
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
#endif /* __NFIT_H__ */
+2 −0
Original line number Diff line number Diff line
@@ -217,6 +217,8 @@ long nvdimm_clear_poison(struct device *dev, phys_addr_t phys,
		return rc;
	if (cmd_rc < 0)
		return cmd_rc;

	nvdimm_clear_from_poison_list(nvdimm_bus, phys, len);
	return clear_err.cleared;
}
EXPORT_SYMBOL_GPL(nvdimm_clear_poison);
+69 −4
Original line number Diff line number Diff line
@@ -547,11 +547,12 @@ void nvdimm_badblocks_populate(struct nd_region *nd_region,
}
EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate);

static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length)
static int add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length,
			gfp_t flags)
{
	struct nd_poison *pl;

	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
	pl = kzalloc(sizeof(*pl), flags);
	if (!pl)
		return -ENOMEM;

@@ -567,7 +568,7 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length)
	struct nd_poison *pl;

	if (list_empty(&nvdimm_bus->poison_list))
		return add_poison(nvdimm_bus, addr, length);
		return add_poison(nvdimm_bus, addr, length, GFP_KERNEL);

	/*
	 * There is a chance this is a duplicate, check for those first.
@@ -587,7 +588,7 @@ static int bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length)
	 * as any overlapping ranges will get resolved when the list is consumed
	 * and converted to badblocks
	 */
	return add_poison(nvdimm_bus, addr, length);
	return add_poison(nvdimm_bus, addr, length, GFP_KERNEL);
}

int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length)
@@ -602,6 +603,70 @@ int nvdimm_bus_add_poison(struct nvdimm_bus *nvdimm_bus, u64 addr, u64 length)
}
EXPORT_SYMBOL_GPL(nvdimm_bus_add_poison);

void nvdimm_clear_from_poison_list(struct nvdimm_bus *nvdimm_bus,
		phys_addr_t start, unsigned int len)
{
	struct list_head *poison_list = &nvdimm_bus->poison_list;
	u64 clr_end = start + len - 1;
	struct nd_poison *pl, *next;

	nvdimm_bus_lock(&nvdimm_bus->dev);
	WARN_ON_ONCE(list_empty(poison_list));

	/*
	 * [start, clr_end] is the poison interval being cleared.
	 * [pl->start, pl_end] is the poison_list entry we're comparing
	 * the above interval against. The poison list entry may need
	 * to be modified (update either start or length), deleted, or
	 * split into two based on the overlap characteristics
	 */

	list_for_each_entry_safe(pl, next, poison_list, list) {
		u64 pl_end = pl->start + pl->length - 1;

		/* Skip intervals with no intersection */
		if (pl_end < start)
			continue;
		if (pl->start >  clr_end)
			continue;
		/* Delete completely overlapped poison entries */
		if ((pl->start >= start) && (pl_end <= clr_end)) {
			list_del(&pl->list);
			kfree(pl);
			continue;
		}
		/* Adjust start point of partially cleared entries */
		if ((start <= pl->start) && (clr_end > pl->start)) {
			pl->length -= clr_end - pl->start + 1;
			pl->start = clr_end + 1;
			continue;
		}
		/* Adjust pl->length for partial clearing at the tail end */
		if ((pl->start < start) && (pl_end <= clr_end)) {
			/* pl->start remains the same */
			pl->length = start - pl->start;
			continue;
		}
		/*
		 * If clearing in the middle of an entry, we split it into
		 * two by modifying the current entry to represent one half of
		 * the split, and adding a new entry for the second half.
		 */
		if ((pl->start < start) && (pl_end > clr_end)) {
			u64 new_start = clr_end + 1;
			u64 new_len = pl_end - new_start + 1;

			/* Add new entry covering the right half */
			add_poison(nvdimm_bus, new_start, new_len, GFP_NOIO);
			/* Adjust this entry to cover the left half */
			pl->length = start - pl->start;
			continue;
		}
	}
	nvdimm_bus_unlock(&nvdimm_bus->dev);
}
EXPORT_SYMBOL_GPL(nvdimm_clear_from_poison_list);

#ifdef CONFIG_BLK_DEV_INTEGRITY
int nd_integrity_init(struct gendisk *disk, unsigned long meta_size)
{
Loading