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