162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * NFIT - Machine Check Handler
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright(c) 2013-2016 Intel Corporation. All rights reserved.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/notifier.h>
862306a36Sopenharmony_ci#include <linux/acpi.h>
962306a36Sopenharmony_ci#include <linux/nd.h>
1062306a36Sopenharmony_ci#include <asm/mce.h>
1162306a36Sopenharmony_ci#include "nfit.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cistatic int nfit_handle_mce(struct notifier_block *nb, unsigned long val,
1462306a36Sopenharmony_ci			void *data)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	struct mce *mce = (struct mce *)data;
1762306a36Sopenharmony_ci	struct acpi_nfit_desc *acpi_desc;
1862306a36Sopenharmony_ci	struct nfit_spa *nfit_spa;
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci	/* We only care about uncorrectable memory errors */
2162306a36Sopenharmony_ci	if (!mce_is_memory_error(mce) || mce_is_correctable(mce))
2262306a36Sopenharmony_ci		return NOTIFY_DONE;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	/* Verify the address reported in the MCE is valid. */
2562306a36Sopenharmony_ci	if (!mce_usable_address(mce))
2662306a36Sopenharmony_ci		return NOTIFY_DONE;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	/*
2962306a36Sopenharmony_ci	 * mce->addr contains the physical addr accessed that caused the
3062306a36Sopenharmony_ci	 * machine check. We need to walk through the list of NFITs, and see
3162306a36Sopenharmony_ci	 * if any of them matches that address, and only then start a scrub.
3262306a36Sopenharmony_ci	 */
3362306a36Sopenharmony_ci	mutex_lock(&acpi_desc_lock);
3462306a36Sopenharmony_ci	list_for_each_entry(acpi_desc, &acpi_descs, list) {
3562306a36Sopenharmony_ci		unsigned int align = 1UL << MCI_MISC_ADDR_LSB(mce->misc);
3662306a36Sopenharmony_ci		struct device *dev = acpi_desc->dev;
3762306a36Sopenharmony_ci		int found_match = 0;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci		mutex_lock(&acpi_desc->init_mutex);
4062306a36Sopenharmony_ci		list_for_each_entry(nfit_spa, &acpi_desc->spas, list) {
4162306a36Sopenharmony_ci			struct acpi_nfit_system_address *spa = nfit_spa->spa;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci			if (nfit_spa_type(spa) != NFIT_SPA_PM)
4462306a36Sopenharmony_ci				continue;
4562306a36Sopenharmony_ci			/* find the spa that covers the mce addr */
4662306a36Sopenharmony_ci			if (spa->address > mce->addr)
4762306a36Sopenharmony_ci				continue;
4862306a36Sopenharmony_ci			if ((spa->address + spa->length - 1) < mce->addr)
4962306a36Sopenharmony_ci				continue;
5062306a36Sopenharmony_ci			found_match = 1;
5162306a36Sopenharmony_ci			dev_dbg(dev, "addr in SPA %d (0x%llx, 0x%llx)\n",
5262306a36Sopenharmony_ci				spa->range_index, spa->address, spa->length);
5362306a36Sopenharmony_ci			/*
5462306a36Sopenharmony_ci			 * We can break at the first match because we're going
5562306a36Sopenharmony_ci			 * to rescan all the SPA ranges. There shouldn't be any
5662306a36Sopenharmony_ci			 * aliasing anyway.
5762306a36Sopenharmony_ci			 */
5862306a36Sopenharmony_ci			break;
5962306a36Sopenharmony_ci		}
6062306a36Sopenharmony_ci		mutex_unlock(&acpi_desc->init_mutex);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		if (!found_match)
6362306a36Sopenharmony_ci			continue;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		/* If this fails due to an -ENOMEM, there is little we can do */
6662306a36Sopenharmony_ci		nvdimm_bus_add_badrange(acpi_desc->nvdimm_bus,
6762306a36Sopenharmony_ci				ALIGN_DOWN(mce->addr, align), align);
6862306a36Sopenharmony_ci		nvdimm_region_notify(nfit_spa->nd_region,
6962306a36Sopenharmony_ci				NVDIMM_REVALIDATE_POISON);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (acpi_desc->scrub_mode == HW_ERROR_SCRUB_ON) {
7262306a36Sopenharmony_ci			/*
7362306a36Sopenharmony_ci			 * We can ignore an -EBUSY here because if an ARS is
7462306a36Sopenharmony_ci			 * already in progress, just let that be the last
7562306a36Sopenharmony_ci			 * authoritative one
7662306a36Sopenharmony_ci			 */
7762306a36Sopenharmony_ci			acpi_nfit_ars_rescan(acpi_desc, 0);
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci		mce->kflags |= MCE_HANDLED_NFIT;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	mutex_unlock(&acpi_desc_lock);
8462306a36Sopenharmony_ci	return NOTIFY_DONE;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic struct notifier_block nfit_mce_dec = {
8862306a36Sopenharmony_ci	.notifier_call	= nfit_handle_mce,
8962306a36Sopenharmony_ci	.priority	= MCE_PRIO_NFIT,
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_civoid nfit_mce_register(void)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	mce_register_decode_chain(&nfit_mce_dec);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_civoid nfit_mce_unregister(void)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	mce_unregister_decode_chain(&nfit_mce_dec);
10062306a36Sopenharmony_ci}
101