18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * NFIT - Machine Check Handler
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright(c) 2013-2016 Intel Corporation. All rights reserved.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/notifier.h>
88c2ecf20Sopenharmony_ci#include <linux/acpi.h>
98c2ecf20Sopenharmony_ci#include <linux/nd.h>
108c2ecf20Sopenharmony_ci#include <asm/mce.h>
118c2ecf20Sopenharmony_ci#include "nfit.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistatic int nfit_handle_mce(struct notifier_block *nb, unsigned long val,
148c2ecf20Sopenharmony_ci			void *data)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	struct mce *mce = (struct mce *)data;
178c2ecf20Sopenharmony_ci	struct acpi_nfit_desc *acpi_desc;
188c2ecf20Sopenharmony_ci	struct nfit_spa *nfit_spa;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	/* We only care about uncorrectable memory errors */
218c2ecf20Sopenharmony_ci	if (!mce_is_memory_error(mce) || mce_is_correctable(mce))
228c2ecf20Sopenharmony_ci		return NOTIFY_DONE;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	/* Verify the address reported in the MCE is valid. */
258c2ecf20Sopenharmony_ci	if (!mce_usable_address(mce))
268c2ecf20Sopenharmony_ci		return NOTIFY_DONE;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	/*
298c2ecf20Sopenharmony_ci	 * mce->addr contains the physical addr accessed that caused the
308c2ecf20Sopenharmony_ci	 * machine check. We need to walk through the list of NFITs, and see
318c2ecf20Sopenharmony_ci	 * if any of them matches that address, and only then start a scrub.
328c2ecf20Sopenharmony_ci	 */
338c2ecf20Sopenharmony_ci	mutex_lock(&acpi_desc_lock);
348c2ecf20Sopenharmony_ci	list_for_each_entry(acpi_desc, &acpi_descs, list) {
358c2ecf20Sopenharmony_ci		struct device *dev = acpi_desc->dev;
368c2ecf20Sopenharmony_ci		int found_match = 0;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci		mutex_lock(&acpi_desc->init_mutex);
398c2ecf20Sopenharmony_ci		list_for_each_entry(nfit_spa, &acpi_desc->spas, list) {
408c2ecf20Sopenharmony_ci			struct acpi_nfit_system_address *spa = nfit_spa->spa;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci			if (nfit_spa_type(spa) != NFIT_SPA_PM)
438c2ecf20Sopenharmony_ci				continue;
448c2ecf20Sopenharmony_ci			/* find the spa that covers the mce addr */
458c2ecf20Sopenharmony_ci			if (spa->address > mce->addr)
468c2ecf20Sopenharmony_ci				continue;
478c2ecf20Sopenharmony_ci			if ((spa->address + spa->length - 1) < mce->addr)
488c2ecf20Sopenharmony_ci				continue;
498c2ecf20Sopenharmony_ci			found_match = 1;
508c2ecf20Sopenharmony_ci			dev_dbg(dev, "addr in SPA %d (0x%llx, 0x%llx)\n",
518c2ecf20Sopenharmony_ci				spa->range_index, spa->address, spa->length);
528c2ecf20Sopenharmony_ci			/*
538c2ecf20Sopenharmony_ci			 * We can break at the first match because we're going
548c2ecf20Sopenharmony_ci			 * to rescan all the SPA ranges. There shouldn't be any
558c2ecf20Sopenharmony_ci			 * aliasing anyway.
568c2ecf20Sopenharmony_ci			 */
578c2ecf20Sopenharmony_ci			break;
588c2ecf20Sopenharmony_ci		}
598c2ecf20Sopenharmony_ci		mutex_unlock(&acpi_desc->init_mutex);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci		if (!found_match)
628c2ecf20Sopenharmony_ci			continue;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci		/* If this fails due to an -ENOMEM, there is little we can do */
658c2ecf20Sopenharmony_ci		nvdimm_bus_add_badrange(acpi_desc->nvdimm_bus,
668c2ecf20Sopenharmony_ci				ALIGN(mce->addr, L1_CACHE_BYTES),
678c2ecf20Sopenharmony_ci				L1_CACHE_BYTES);
688c2ecf20Sopenharmony_ci		nvdimm_region_notify(nfit_spa->nd_region,
698c2ecf20Sopenharmony_ci				NVDIMM_REVALIDATE_POISON);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		if (acpi_desc->scrub_mode == HW_ERROR_SCRUB_ON) {
728c2ecf20Sopenharmony_ci			/*
738c2ecf20Sopenharmony_ci			 * We can ignore an -EBUSY here because if an ARS is
748c2ecf20Sopenharmony_ci			 * already in progress, just let that be the last
758c2ecf20Sopenharmony_ci			 * authoritative one
768c2ecf20Sopenharmony_ci			 */
778c2ecf20Sopenharmony_ci			acpi_nfit_ars_rescan(acpi_desc, 0);
788c2ecf20Sopenharmony_ci		}
798c2ecf20Sopenharmony_ci		mce->kflags |= MCE_HANDLED_NFIT;
808c2ecf20Sopenharmony_ci		break;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	mutex_unlock(&acpi_desc_lock);
848c2ecf20Sopenharmony_ci	return NOTIFY_DONE;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic struct notifier_block nfit_mce_dec = {
888c2ecf20Sopenharmony_ci	.notifier_call	= nfit_handle_mce,
898c2ecf20Sopenharmony_ci	.priority	= MCE_PRIO_NFIT,
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_civoid nfit_mce_register(void)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	mce_register_decode_chain(&nfit_mce_dec);
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_civoid nfit_mce_unregister(void)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	mce_unregister_decode_chain(&nfit_mce_dec);
1008c2ecf20Sopenharmony_ci}
101