18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * GHES/EDAC Linux driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2013 by Mauro Carvalho Chehab 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Red Hat Inc. https://www.redhat.com 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <acpi/ghes.h> 138c2ecf20Sopenharmony_ci#include <linux/edac.h> 148c2ecf20Sopenharmony_ci#include <linux/dmi.h> 158c2ecf20Sopenharmony_ci#include "edac_module.h" 168c2ecf20Sopenharmony_ci#include <ras/ras_event.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct ghes_pvt { 198c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci /* Buffers for the error handling routine */ 228c2ecf20Sopenharmony_ci char other_detail[400]; 238c2ecf20Sopenharmony_ci char msg[80]; 248c2ecf20Sopenharmony_ci}; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic refcount_t ghes_refcount = REFCOUNT_INIT(0); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* 298c2ecf20Sopenharmony_ci * Access to ghes_pvt must be protected by ghes_lock. The spinlock 308c2ecf20Sopenharmony_ci * also provides the necessary (implicit) memory barrier for the SMP 318c2ecf20Sopenharmony_ci * case to make the pointer visible on another CPU. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_cistatic struct ghes_pvt *ghes_pvt; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* 368c2ecf20Sopenharmony_ci * This driver's representation of the system hardware, as collected 378c2ecf20Sopenharmony_ci * from DMI. 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_cistruct ghes_hw_desc { 408c2ecf20Sopenharmony_ci int num_dimms; 418c2ecf20Sopenharmony_ci struct dimm_info *dimms; 428c2ecf20Sopenharmony_ci} ghes_hw; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/* GHES registration mutex */ 458c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(ghes_reg_mutex); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* 488c2ecf20Sopenharmony_ci * Sync with other, potentially concurrent callers of 498c2ecf20Sopenharmony_ci * ghes_edac_report_mem_error(). We don't know what the 508c2ecf20Sopenharmony_ci * "inventive" firmware would do. 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(ghes_lock); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* "ghes_edac.force_load=1" skips the platform check */ 558c2ecf20Sopenharmony_cistatic bool __read_mostly force_load; 568c2ecf20Sopenharmony_cimodule_param(force_load, bool, 0); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic bool system_scanned; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci/* Memory Device - Type 17 of SMBIOS spec */ 618c2ecf20Sopenharmony_cistruct memdev_dmi_entry { 628c2ecf20Sopenharmony_ci u8 type; 638c2ecf20Sopenharmony_ci u8 length; 648c2ecf20Sopenharmony_ci u16 handle; 658c2ecf20Sopenharmony_ci u16 phys_mem_array_handle; 668c2ecf20Sopenharmony_ci u16 mem_err_info_handle; 678c2ecf20Sopenharmony_ci u16 total_width; 688c2ecf20Sopenharmony_ci u16 data_width; 698c2ecf20Sopenharmony_ci u16 size; 708c2ecf20Sopenharmony_ci u8 form_factor; 718c2ecf20Sopenharmony_ci u8 device_set; 728c2ecf20Sopenharmony_ci u8 device_locator; 738c2ecf20Sopenharmony_ci u8 bank_locator; 748c2ecf20Sopenharmony_ci u8 memory_type; 758c2ecf20Sopenharmony_ci u16 type_detail; 768c2ecf20Sopenharmony_ci u16 speed; 778c2ecf20Sopenharmony_ci u8 manufacturer; 788c2ecf20Sopenharmony_ci u8 serial_number; 798c2ecf20Sopenharmony_ci u8 asset_tag; 808c2ecf20Sopenharmony_ci u8 part_number; 818c2ecf20Sopenharmony_ci u8 attributes; 828c2ecf20Sopenharmony_ci u32 extended_size; 838c2ecf20Sopenharmony_ci u16 conf_mem_clk_speed; 848c2ecf20Sopenharmony_ci} __attribute__((__packed__)); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic struct dimm_info *find_dimm_by_handle(struct mem_ctl_info *mci, u16 handle) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct dimm_info *dimm; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci mci_for_each_dimm(mci, dimm) { 918c2ecf20Sopenharmony_ci if (dimm->smbios_handle == handle) 928c2ecf20Sopenharmony_ci return dimm; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return NULL; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic void dimm_setup_label(struct dimm_info *dimm, u16 handle) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci const char *bank = NULL, *device = NULL; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci dmi_memdev_name(handle, &bank, &device); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* 1058c2ecf20Sopenharmony_ci * Set to a NULL string when both bank and device are zero. In this case, 1068c2ecf20Sopenharmony_ci * the label assigned by default will be preserved. 1078c2ecf20Sopenharmony_ci */ 1088c2ecf20Sopenharmony_ci snprintf(dimm->label, sizeof(dimm->label), "%s%s%s", 1098c2ecf20Sopenharmony_ci (bank && *bank) ? bank : "", 1108c2ecf20Sopenharmony_ci (bank && *bank && device && *device) ? " " : "", 1118c2ecf20Sopenharmony_ci (device && *device) ? device : ""); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic void assign_dmi_dimm_info(struct dimm_info *dimm, struct memdev_dmi_entry *entry) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci u16 rdr_mask = BIT(7) | BIT(13); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if (entry->size == 0xffff) { 1198c2ecf20Sopenharmony_ci pr_info("Can't get DIMM%i size\n", dimm->idx); 1208c2ecf20Sopenharmony_ci dimm->nr_pages = MiB_TO_PAGES(32);/* Unknown */ 1218c2ecf20Sopenharmony_ci } else if (entry->size == 0x7fff) { 1228c2ecf20Sopenharmony_ci dimm->nr_pages = MiB_TO_PAGES(entry->extended_size); 1238c2ecf20Sopenharmony_ci } else { 1248c2ecf20Sopenharmony_ci if (entry->size & BIT(15)) 1258c2ecf20Sopenharmony_ci dimm->nr_pages = MiB_TO_PAGES((entry->size & 0x7fff) << 10); 1268c2ecf20Sopenharmony_ci else 1278c2ecf20Sopenharmony_ci dimm->nr_pages = MiB_TO_PAGES(entry->size); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci switch (entry->memory_type) { 1318c2ecf20Sopenharmony_ci case 0x12: 1328c2ecf20Sopenharmony_ci if (entry->type_detail & BIT(13)) 1338c2ecf20Sopenharmony_ci dimm->mtype = MEM_RDDR; 1348c2ecf20Sopenharmony_ci else 1358c2ecf20Sopenharmony_ci dimm->mtype = MEM_DDR; 1368c2ecf20Sopenharmony_ci break; 1378c2ecf20Sopenharmony_ci case 0x13: 1388c2ecf20Sopenharmony_ci if (entry->type_detail & BIT(13)) 1398c2ecf20Sopenharmony_ci dimm->mtype = MEM_RDDR2; 1408c2ecf20Sopenharmony_ci else 1418c2ecf20Sopenharmony_ci dimm->mtype = MEM_DDR2; 1428c2ecf20Sopenharmony_ci break; 1438c2ecf20Sopenharmony_ci case 0x14: 1448c2ecf20Sopenharmony_ci dimm->mtype = MEM_FB_DDR2; 1458c2ecf20Sopenharmony_ci break; 1468c2ecf20Sopenharmony_ci case 0x18: 1478c2ecf20Sopenharmony_ci if (entry->type_detail & BIT(12)) 1488c2ecf20Sopenharmony_ci dimm->mtype = MEM_NVDIMM; 1498c2ecf20Sopenharmony_ci else if (entry->type_detail & BIT(13)) 1508c2ecf20Sopenharmony_ci dimm->mtype = MEM_RDDR3; 1518c2ecf20Sopenharmony_ci else 1528c2ecf20Sopenharmony_ci dimm->mtype = MEM_DDR3; 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci case 0x1a: 1558c2ecf20Sopenharmony_ci if (entry->type_detail & BIT(12)) 1568c2ecf20Sopenharmony_ci dimm->mtype = MEM_NVDIMM; 1578c2ecf20Sopenharmony_ci else if (entry->type_detail & BIT(13)) 1588c2ecf20Sopenharmony_ci dimm->mtype = MEM_RDDR4; 1598c2ecf20Sopenharmony_ci else 1608c2ecf20Sopenharmony_ci dimm->mtype = MEM_DDR4; 1618c2ecf20Sopenharmony_ci break; 1628c2ecf20Sopenharmony_ci default: 1638c2ecf20Sopenharmony_ci if (entry->type_detail & BIT(6)) 1648c2ecf20Sopenharmony_ci dimm->mtype = MEM_RMBS; 1658c2ecf20Sopenharmony_ci else if ((entry->type_detail & rdr_mask) == rdr_mask) 1668c2ecf20Sopenharmony_ci dimm->mtype = MEM_RDR; 1678c2ecf20Sopenharmony_ci else if (entry->type_detail & BIT(7)) 1688c2ecf20Sopenharmony_ci dimm->mtype = MEM_SDR; 1698c2ecf20Sopenharmony_ci else if (entry->type_detail & BIT(9)) 1708c2ecf20Sopenharmony_ci dimm->mtype = MEM_EDO; 1718c2ecf20Sopenharmony_ci else 1728c2ecf20Sopenharmony_ci dimm->mtype = MEM_UNKNOWN; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* 1768c2ecf20Sopenharmony_ci * Actually, we can only detect if the memory has bits for 1778c2ecf20Sopenharmony_ci * checksum or not 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_ci if (entry->total_width == entry->data_width) 1808c2ecf20Sopenharmony_ci dimm->edac_mode = EDAC_NONE; 1818c2ecf20Sopenharmony_ci else 1828c2ecf20Sopenharmony_ci dimm->edac_mode = EDAC_SECDED; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci dimm->dtype = DEV_UNKNOWN; 1858c2ecf20Sopenharmony_ci dimm->grain = 128; /* Likely, worse case */ 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci dimm_setup_label(dimm, entry->handle); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (dimm->nr_pages) { 1908c2ecf20Sopenharmony_ci edac_dbg(1, "DIMM%i: %s size = %d MB%s\n", 1918c2ecf20Sopenharmony_ci dimm->idx, edac_mem_types[dimm->mtype], 1928c2ecf20Sopenharmony_ci PAGES_TO_MiB(dimm->nr_pages), 1938c2ecf20Sopenharmony_ci (dimm->edac_mode != EDAC_NONE) ? "(ECC)" : ""); 1948c2ecf20Sopenharmony_ci edac_dbg(2, "\ttype %d, detail 0x%02x, width %d(total %d)\n", 1958c2ecf20Sopenharmony_ci entry->memory_type, entry->type_detail, 1968c2ecf20Sopenharmony_ci entry->total_width, entry->data_width); 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci dimm->smbios_handle = entry->handle; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic void enumerate_dimms(const struct dmi_header *dh, void *arg) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct memdev_dmi_entry *entry = (struct memdev_dmi_entry *)dh; 2058c2ecf20Sopenharmony_ci struct ghes_hw_desc *hw = (struct ghes_hw_desc *)arg; 2068c2ecf20Sopenharmony_ci struct dimm_info *d; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (dh->type != DMI_ENTRY_MEM_DEVICE) 2098c2ecf20Sopenharmony_ci return; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* Enlarge the array with additional 16 */ 2128c2ecf20Sopenharmony_ci if (!hw->num_dimms || !(hw->num_dimms % 16)) { 2138c2ecf20Sopenharmony_ci struct dimm_info *new; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci new = krealloc(hw->dimms, (hw->num_dimms + 16) * sizeof(struct dimm_info), 2168c2ecf20Sopenharmony_ci GFP_KERNEL); 2178c2ecf20Sopenharmony_ci if (!new) { 2188c2ecf20Sopenharmony_ci WARN_ON_ONCE(1); 2198c2ecf20Sopenharmony_ci return; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci hw->dimms = new; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci d = &hw->dimms[hw->num_dimms]; 2268c2ecf20Sopenharmony_ci d->idx = hw->num_dimms; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci assign_dmi_dimm_info(d, entry); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci hw->num_dimms++; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic void ghes_scan_system(void) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci if (system_scanned) 2368c2ecf20Sopenharmony_ci return; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci dmi_walk(enumerate_dimms, &ghes_hw); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci system_scanned = true; 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_civoid ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci struct edac_raw_error_desc *e; 2468c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 2478c2ecf20Sopenharmony_ci struct ghes_pvt *pvt; 2488c2ecf20Sopenharmony_ci unsigned long flags; 2498c2ecf20Sopenharmony_ci char *p; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci /* 2528c2ecf20Sopenharmony_ci * We can do the locking below because GHES defers error processing 2538c2ecf20Sopenharmony_ci * from NMI to IRQ context. Whenever that changes, we'd at least 2548c2ecf20Sopenharmony_ci * know. 2558c2ecf20Sopenharmony_ci */ 2568c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(in_nmi())) 2578c2ecf20Sopenharmony_ci return; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_lock, flags); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci pvt = ghes_pvt; 2628c2ecf20Sopenharmony_ci if (!pvt) 2638c2ecf20Sopenharmony_ci goto unlock; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci mci = pvt->mci; 2668c2ecf20Sopenharmony_ci e = &mci->error_desc; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci /* Cleans the error report buffer */ 2698c2ecf20Sopenharmony_ci memset(e, 0, sizeof (*e)); 2708c2ecf20Sopenharmony_ci e->error_count = 1; 2718c2ecf20Sopenharmony_ci e->grain = 1; 2728c2ecf20Sopenharmony_ci e->msg = pvt->msg; 2738c2ecf20Sopenharmony_ci e->other_detail = pvt->other_detail; 2748c2ecf20Sopenharmony_ci e->top_layer = -1; 2758c2ecf20Sopenharmony_ci e->mid_layer = -1; 2768c2ecf20Sopenharmony_ci e->low_layer = -1; 2778c2ecf20Sopenharmony_ci *pvt->other_detail = '\0'; 2788c2ecf20Sopenharmony_ci *pvt->msg = '\0'; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci switch (sev) { 2818c2ecf20Sopenharmony_ci case GHES_SEV_CORRECTED: 2828c2ecf20Sopenharmony_ci e->type = HW_EVENT_ERR_CORRECTED; 2838c2ecf20Sopenharmony_ci break; 2848c2ecf20Sopenharmony_ci case GHES_SEV_RECOVERABLE: 2858c2ecf20Sopenharmony_ci e->type = HW_EVENT_ERR_UNCORRECTED; 2868c2ecf20Sopenharmony_ci break; 2878c2ecf20Sopenharmony_ci case GHES_SEV_PANIC: 2888c2ecf20Sopenharmony_ci e->type = HW_EVENT_ERR_FATAL; 2898c2ecf20Sopenharmony_ci break; 2908c2ecf20Sopenharmony_ci default: 2918c2ecf20Sopenharmony_ci case GHES_SEV_NO: 2928c2ecf20Sopenharmony_ci e->type = HW_EVENT_ERR_INFO; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci edac_dbg(1, "error validation_bits: 0x%08llx\n", 2968c2ecf20Sopenharmony_ci (long long)mem_err->validation_bits); 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci /* Error type, mapped on e->msg */ 2998c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { 3008c2ecf20Sopenharmony_ci p = pvt->msg; 3018c2ecf20Sopenharmony_ci switch (mem_err->error_type) { 3028c2ecf20Sopenharmony_ci case 0: 3038c2ecf20Sopenharmony_ci p += sprintf(p, "Unknown"); 3048c2ecf20Sopenharmony_ci break; 3058c2ecf20Sopenharmony_ci case 1: 3068c2ecf20Sopenharmony_ci p += sprintf(p, "No error"); 3078c2ecf20Sopenharmony_ci break; 3088c2ecf20Sopenharmony_ci case 2: 3098c2ecf20Sopenharmony_ci p += sprintf(p, "Single-bit ECC"); 3108c2ecf20Sopenharmony_ci break; 3118c2ecf20Sopenharmony_ci case 3: 3128c2ecf20Sopenharmony_ci p += sprintf(p, "Multi-bit ECC"); 3138c2ecf20Sopenharmony_ci break; 3148c2ecf20Sopenharmony_ci case 4: 3158c2ecf20Sopenharmony_ci p += sprintf(p, "Single-symbol ChipKill ECC"); 3168c2ecf20Sopenharmony_ci break; 3178c2ecf20Sopenharmony_ci case 5: 3188c2ecf20Sopenharmony_ci p += sprintf(p, "Multi-symbol ChipKill ECC"); 3198c2ecf20Sopenharmony_ci break; 3208c2ecf20Sopenharmony_ci case 6: 3218c2ecf20Sopenharmony_ci p += sprintf(p, "Master abort"); 3228c2ecf20Sopenharmony_ci break; 3238c2ecf20Sopenharmony_ci case 7: 3248c2ecf20Sopenharmony_ci p += sprintf(p, "Target abort"); 3258c2ecf20Sopenharmony_ci break; 3268c2ecf20Sopenharmony_ci case 8: 3278c2ecf20Sopenharmony_ci p += sprintf(p, "Parity Error"); 3288c2ecf20Sopenharmony_ci break; 3298c2ecf20Sopenharmony_ci case 9: 3308c2ecf20Sopenharmony_ci p += sprintf(p, "Watchdog timeout"); 3318c2ecf20Sopenharmony_ci break; 3328c2ecf20Sopenharmony_ci case 10: 3338c2ecf20Sopenharmony_ci p += sprintf(p, "Invalid address"); 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci case 11: 3368c2ecf20Sopenharmony_ci p += sprintf(p, "Mirror Broken"); 3378c2ecf20Sopenharmony_ci break; 3388c2ecf20Sopenharmony_ci case 12: 3398c2ecf20Sopenharmony_ci p += sprintf(p, "Memory Sparing"); 3408c2ecf20Sopenharmony_ci break; 3418c2ecf20Sopenharmony_ci case 13: 3428c2ecf20Sopenharmony_ci p += sprintf(p, "Scrub corrected error"); 3438c2ecf20Sopenharmony_ci break; 3448c2ecf20Sopenharmony_ci case 14: 3458c2ecf20Sopenharmony_ci p += sprintf(p, "Scrub uncorrected error"); 3468c2ecf20Sopenharmony_ci break; 3478c2ecf20Sopenharmony_ci case 15: 3488c2ecf20Sopenharmony_ci p += sprintf(p, "Physical Memory Map-out event"); 3498c2ecf20Sopenharmony_ci break; 3508c2ecf20Sopenharmony_ci default: 3518c2ecf20Sopenharmony_ci p += sprintf(p, "reserved error (%d)", 3528c2ecf20Sopenharmony_ci mem_err->error_type); 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci } else { 3558c2ecf20Sopenharmony_ci strcpy(pvt->msg, "unknown error"); 3568c2ecf20Sopenharmony_ci } 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci /* Error address */ 3598c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_PA) { 3608c2ecf20Sopenharmony_ci e->page_frame_number = PHYS_PFN(mem_err->physical_addr); 3618c2ecf20Sopenharmony_ci e->offset_in_page = offset_in_page(mem_err->physical_addr); 3628c2ecf20Sopenharmony_ci } 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci /* Error grain */ 3658c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_PA_MASK) 3668c2ecf20Sopenharmony_ci e->grain = ~mem_err->physical_addr_mask + 1; 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci /* Memory error location, mapped on e->location */ 3698c2ecf20Sopenharmony_ci p = e->location; 3708c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_NODE) 3718c2ecf20Sopenharmony_ci p += sprintf(p, "node:%d ", mem_err->node); 3728c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_CARD) 3738c2ecf20Sopenharmony_ci p += sprintf(p, "card:%d ", mem_err->card); 3748c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_MODULE) 3758c2ecf20Sopenharmony_ci p += sprintf(p, "module:%d ", mem_err->module); 3768c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_RANK_NUMBER) 3778c2ecf20Sopenharmony_ci p += sprintf(p, "rank:%d ", mem_err->rank); 3788c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_BANK) 3798c2ecf20Sopenharmony_ci p += sprintf(p, "bank:%d ", mem_err->bank); 3808c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_BANK_GROUP) 3818c2ecf20Sopenharmony_ci p += sprintf(p, "bank_group:%d ", 3828c2ecf20Sopenharmony_ci mem_err->bank >> CPER_MEM_BANK_GROUP_SHIFT); 3838c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_BANK_ADDRESS) 3848c2ecf20Sopenharmony_ci p += sprintf(p, "bank_address:%d ", 3858c2ecf20Sopenharmony_ci mem_err->bank & CPER_MEM_BANK_ADDRESS_MASK); 3868c2ecf20Sopenharmony_ci if (mem_err->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) { 3878c2ecf20Sopenharmony_ci u32 row = mem_err->row; 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci row |= cper_get_mem_extension(mem_err->validation_bits, mem_err->extended); 3908c2ecf20Sopenharmony_ci p += sprintf(p, "row:%d ", row); 3918c2ecf20Sopenharmony_ci } 3928c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_COLUMN) 3938c2ecf20Sopenharmony_ci p += sprintf(p, "col:%d ", mem_err->column); 3948c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_BIT_POSITION) 3958c2ecf20Sopenharmony_ci p += sprintf(p, "bit_pos:%d ", mem_err->bit_pos); 3968c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) { 3978c2ecf20Sopenharmony_ci const char *bank = NULL, *device = NULL; 3988c2ecf20Sopenharmony_ci struct dimm_info *dimm; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci dmi_memdev_name(mem_err->mem_dev_handle, &bank, &device); 4018c2ecf20Sopenharmony_ci if (bank != NULL && device != NULL) 4028c2ecf20Sopenharmony_ci p += sprintf(p, "DIMM location:%s %s ", bank, device); 4038c2ecf20Sopenharmony_ci else 4048c2ecf20Sopenharmony_ci p += sprintf(p, "DIMM DMI handle: 0x%.4x ", 4058c2ecf20Sopenharmony_ci mem_err->mem_dev_handle); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci dimm = find_dimm_by_handle(mci, mem_err->mem_dev_handle); 4088c2ecf20Sopenharmony_ci if (dimm) { 4098c2ecf20Sopenharmony_ci e->top_layer = dimm->idx; 4108c2ecf20Sopenharmony_ci strcpy(e->label, dimm->label); 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci } 4138c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_CHIP_ID) 4148c2ecf20Sopenharmony_ci p += sprintf(p, "chipID: %d ", 4158c2ecf20Sopenharmony_ci mem_err->extended >> CPER_MEM_CHIP_ID_SHIFT); 4168c2ecf20Sopenharmony_ci if (p > e->location) 4178c2ecf20Sopenharmony_ci *(p - 1) = '\0'; 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci if (!*e->label) 4208c2ecf20Sopenharmony_ci strcpy(e->label, "unknown memory"); 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci /* All other fields are mapped on e->other_detail */ 4238c2ecf20Sopenharmony_ci p = pvt->other_detail; 4248c2ecf20Sopenharmony_ci p += snprintf(p, sizeof(pvt->other_detail), 4258c2ecf20Sopenharmony_ci "APEI location: %s ", e->location); 4268c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_STATUS) { 4278c2ecf20Sopenharmony_ci u64 status = mem_err->error_status; 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci p += sprintf(p, "status(0x%016llx): ", (long long)status); 4308c2ecf20Sopenharmony_ci switch ((status >> 8) & 0xff) { 4318c2ecf20Sopenharmony_ci case 1: 4328c2ecf20Sopenharmony_ci p += sprintf(p, "Error detected internal to the component "); 4338c2ecf20Sopenharmony_ci break; 4348c2ecf20Sopenharmony_ci case 16: 4358c2ecf20Sopenharmony_ci p += sprintf(p, "Error detected in the bus "); 4368c2ecf20Sopenharmony_ci break; 4378c2ecf20Sopenharmony_ci case 4: 4388c2ecf20Sopenharmony_ci p += sprintf(p, "Storage error in DRAM memory "); 4398c2ecf20Sopenharmony_ci break; 4408c2ecf20Sopenharmony_ci case 5: 4418c2ecf20Sopenharmony_ci p += sprintf(p, "Storage error in TLB "); 4428c2ecf20Sopenharmony_ci break; 4438c2ecf20Sopenharmony_ci case 6: 4448c2ecf20Sopenharmony_ci p += sprintf(p, "Storage error in cache "); 4458c2ecf20Sopenharmony_ci break; 4468c2ecf20Sopenharmony_ci case 7: 4478c2ecf20Sopenharmony_ci p += sprintf(p, "Error in one or more functional units "); 4488c2ecf20Sopenharmony_ci break; 4498c2ecf20Sopenharmony_ci case 8: 4508c2ecf20Sopenharmony_ci p += sprintf(p, "component failed self test "); 4518c2ecf20Sopenharmony_ci break; 4528c2ecf20Sopenharmony_ci case 9: 4538c2ecf20Sopenharmony_ci p += sprintf(p, "Overflow or undervalue of internal queue "); 4548c2ecf20Sopenharmony_ci break; 4558c2ecf20Sopenharmony_ci case 17: 4568c2ecf20Sopenharmony_ci p += sprintf(p, "Virtual address not found on IO-TLB or IO-PDIR "); 4578c2ecf20Sopenharmony_ci break; 4588c2ecf20Sopenharmony_ci case 18: 4598c2ecf20Sopenharmony_ci p += sprintf(p, "Improper access error "); 4608c2ecf20Sopenharmony_ci break; 4618c2ecf20Sopenharmony_ci case 19: 4628c2ecf20Sopenharmony_ci p += sprintf(p, "Access to a memory address which is not mapped to any component "); 4638c2ecf20Sopenharmony_ci break; 4648c2ecf20Sopenharmony_ci case 20: 4658c2ecf20Sopenharmony_ci p += sprintf(p, "Loss of Lockstep "); 4668c2ecf20Sopenharmony_ci break; 4678c2ecf20Sopenharmony_ci case 21: 4688c2ecf20Sopenharmony_ci p += sprintf(p, "Response not associated with a request "); 4698c2ecf20Sopenharmony_ci break; 4708c2ecf20Sopenharmony_ci case 22: 4718c2ecf20Sopenharmony_ci p += sprintf(p, "Bus parity error - must also set the A, C, or D Bits "); 4728c2ecf20Sopenharmony_ci break; 4738c2ecf20Sopenharmony_ci case 23: 4748c2ecf20Sopenharmony_ci p += sprintf(p, "Detection of a PATH_ERROR "); 4758c2ecf20Sopenharmony_ci break; 4768c2ecf20Sopenharmony_ci case 25: 4778c2ecf20Sopenharmony_ci p += sprintf(p, "Bus operation timeout "); 4788c2ecf20Sopenharmony_ci break; 4798c2ecf20Sopenharmony_ci case 26: 4808c2ecf20Sopenharmony_ci p += sprintf(p, "A read was issued to data that has been poisoned "); 4818c2ecf20Sopenharmony_ci break; 4828c2ecf20Sopenharmony_ci default: 4838c2ecf20Sopenharmony_ci p += sprintf(p, "reserved "); 4848c2ecf20Sopenharmony_ci break; 4858c2ecf20Sopenharmony_ci } 4868c2ecf20Sopenharmony_ci } 4878c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) 4888c2ecf20Sopenharmony_ci p += sprintf(p, "requestorID: 0x%016llx ", 4898c2ecf20Sopenharmony_ci (long long)mem_err->requestor_id); 4908c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_RESPONDER_ID) 4918c2ecf20Sopenharmony_ci p += sprintf(p, "responderID: 0x%016llx ", 4928c2ecf20Sopenharmony_ci (long long)mem_err->responder_id); 4938c2ecf20Sopenharmony_ci if (mem_err->validation_bits & CPER_MEM_VALID_TARGET_ID) 4948c2ecf20Sopenharmony_ci p += sprintf(p, "targetID: 0x%016llx ", 4958c2ecf20Sopenharmony_ci (long long)mem_err->responder_id); 4968c2ecf20Sopenharmony_ci if (p > pvt->other_detail) 4978c2ecf20Sopenharmony_ci *(p - 1) = '\0'; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci edac_raw_mc_handle_error(e); 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ciunlock: 5028c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_lock, flags); 5038c2ecf20Sopenharmony_ci} 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci/* 5068c2ecf20Sopenharmony_ci * Known systems that are safe to enable this module. 5078c2ecf20Sopenharmony_ci */ 5088c2ecf20Sopenharmony_cistatic struct acpi_platform_list plat_list[] = { 5098c2ecf20Sopenharmony_ci {"HPE ", "Server ", 0, ACPI_SIG_FADT, all_versions}, 5108c2ecf20Sopenharmony_ci { } /* End */ 5118c2ecf20Sopenharmony_ci}; 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ciint ghes_edac_register(struct ghes *ghes, struct device *dev) 5148c2ecf20Sopenharmony_ci{ 5158c2ecf20Sopenharmony_ci bool fake = false; 5168c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 5178c2ecf20Sopenharmony_ci struct ghes_pvt *pvt; 5188c2ecf20Sopenharmony_ci struct edac_mc_layer layers[1]; 5198c2ecf20Sopenharmony_ci unsigned long flags; 5208c2ecf20Sopenharmony_ci int idx = -1; 5218c2ecf20Sopenharmony_ci int rc = 0; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_X86)) { 5248c2ecf20Sopenharmony_ci /* Check if safe to enable on this system */ 5258c2ecf20Sopenharmony_ci idx = acpi_match_platform_list(plat_list); 5268c2ecf20Sopenharmony_ci if (!force_load && idx < 0) 5278c2ecf20Sopenharmony_ci return -ENODEV; 5288c2ecf20Sopenharmony_ci } else { 5298c2ecf20Sopenharmony_ci force_load = true; 5308c2ecf20Sopenharmony_ci idx = 0; 5318c2ecf20Sopenharmony_ci } 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci /* finish another registration/unregistration instance first */ 5348c2ecf20Sopenharmony_ci mutex_lock(&ghes_reg_mutex); 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_ci /* 5378c2ecf20Sopenharmony_ci * We have only one logical memory controller to which all DIMMs belong. 5388c2ecf20Sopenharmony_ci */ 5398c2ecf20Sopenharmony_ci if (refcount_inc_not_zero(&ghes_refcount)) 5408c2ecf20Sopenharmony_ci goto unlock; 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci ghes_scan_system(); 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci /* Check if we've got a bogus BIOS */ 5458c2ecf20Sopenharmony_ci if (!ghes_hw.num_dimms) { 5468c2ecf20Sopenharmony_ci fake = true; 5478c2ecf20Sopenharmony_ci ghes_hw.num_dimms = 1; 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci layers[0].type = EDAC_MC_LAYER_ALL_MEM; 5518c2ecf20Sopenharmony_ci layers[0].size = ghes_hw.num_dimms; 5528c2ecf20Sopenharmony_ci layers[0].is_virt_csrow = true; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(struct ghes_pvt)); 5558c2ecf20Sopenharmony_ci if (!mci) { 5568c2ecf20Sopenharmony_ci pr_info("Can't allocate memory for EDAC data\n"); 5578c2ecf20Sopenharmony_ci rc = -ENOMEM; 5588c2ecf20Sopenharmony_ci goto unlock; 5598c2ecf20Sopenharmony_ci } 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci pvt = mci->pvt_info; 5628c2ecf20Sopenharmony_ci pvt->mci = mci; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci mci->pdev = dev; 5658c2ecf20Sopenharmony_ci mci->mtype_cap = MEM_FLAG_EMPTY; 5668c2ecf20Sopenharmony_ci mci->edac_ctl_cap = EDAC_FLAG_NONE; 5678c2ecf20Sopenharmony_ci mci->edac_cap = EDAC_FLAG_NONE; 5688c2ecf20Sopenharmony_ci mci->mod_name = "ghes_edac.c"; 5698c2ecf20Sopenharmony_ci mci->ctl_name = "ghes_edac"; 5708c2ecf20Sopenharmony_ci mci->dev_name = "ghes"; 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci if (fake) { 5738c2ecf20Sopenharmony_ci pr_info("This system has a very crappy BIOS: It doesn't even list the DIMMS.\n"); 5748c2ecf20Sopenharmony_ci pr_info("Its SMBIOS info is wrong. It is doubtful that the error report would\n"); 5758c2ecf20Sopenharmony_ci pr_info("work on such system. Use this driver with caution\n"); 5768c2ecf20Sopenharmony_ci } else if (idx < 0) { 5778c2ecf20Sopenharmony_ci pr_info("This EDAC driver relies on BIOS to enumerate memory and get error reports.\n"); 5788c2ecf20Sopenharmony_ci pr_info("Unfortunately, not all BIOSes reflect the memory layout correctly.\n"); 5798c2ecf20Sopenharmony_ci pr_info("So, the end result of using this driver varies from vendor to vendor.\n"); 5808c2ecf20Sopenharmony_ci pr_info("If you find incorrect reports, please contact your hardware vendor\n"); 5818c2ecf20Sopenharmony_ci pr_info("to correct its BIOS.\n"); 5828c2ecf20Sopenharmony_ci pr_info("This system has %d DIMM sockets.\n", ghes_hw.num_dimms); 5838c2ecf20Sopenharmony_ci } 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci if (!fake) { 5868c2ecf20Sopenharmony_ci struct dimm_info *src, *dst; 5878c2ecf20Sopenharmony_ci int i = 0; 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci mci_for_each_dimm(mci, dst) { 5908c2ecf20Sopenharmony_ci src = &ghes_hw.dimms[i]; 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci dst->idx = src->idx; 5938c2ecf20Sopenharmony_ci dst->smbios_handle = src->smbios_handle; 5948c2ecf20Sopenharmony_ci dst->nr_pages = src->nr_pages; 5958c2ecf20Sopenharmony_ci dst->mtype = src->mtype; 5968c2ecf20Sopenharmony_ci dst->edac_mode = src->edac_mode; 5978c2ecf20Sopenharmony_ci dst->dtype = src->dtype; 5988c2ecf20Sopenharmony_ci dst->grain = src->grain; 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci /* 6018c2ecf20Sopenharmony_ci * If no src->label, preserve default label assigned 6028c2ecf20Sopenharmony_ci * from EDAC core. 6038c2ecf20Sopenharmony_ci */ 6048c2ecf20Sopenharmony_ci if (strlen(src->label)) 6058c2ecf20Sopenharmony_ci memcpy(dst->label, src->label, sizeof(src->label)); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci i++; 6088c2ecf20Sopenharmony_ci } 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_ci } else { 6118c2ecf20Sopenharmony_ci struct dimm_info *dimm = edac_get_dimm(mci, 0, 0, 0); 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci dimm->nr_pages = 1; 6148c2ecf20Sopenharmony_ci dimm->grain = 128; 6158c2ecf20Sopenharmony_ci dimm->mtype = MEM_UNKNOWN; 6168c2ecf20Sopenharmony_ci dimm->dtype = DEV_UNKNOWN; 6178c2ecf20Sopenharmony_ci dimm->edac_mode = EDAC_SECDED; 6188c2ecf20Sopenharmony_ci } 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci rc = edac_mc_add_mc(mci); 6218c2ecf20Sopenharmony_ci if (rc < 0) { 6228c2ecf20Sopenharmony_ci pr_info("Can't register with the EDAC core\n"); 6238c2ecf20Sopenharmony_ci edac_mc_free(mci); 6248c2ecf20Sopenharmony_ci rc = -ENODEV; 6258c2ecf20Sopenharmony_ci goto unlock; 6268c2ecf20Sopenharmony_ci } 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_lock, flags); 6298c2ecf20Sopenharmony_ci ghes_pvt = pvt; 6308c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_lock, flags); 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci /* only set on success */ 6338c2ecf20Sopenharmony_ci refcount_set(&ghes_refcount, 1); 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_ciunlock: 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci /* Not needed anymore */ 6388c2ecf20Sopenharmony_ci kfree(ghes_hw.dimms); 6398c2ecf20Sopenharmony_ci ghes_hw.dimms = NULL; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci mutex_unlock(&ghes_reg_mutex); 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_ci return rc; 6448c2ecf20Sopenharmony_ci} 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_civoid ghes_edac_unregister(struct ghes *ghes) 6478c2ecf20Sopenharmony_ci{ 6488c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 6498c2ecf20Sopenharmony_ci unsigned long flags; 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci if (!force_load) 6528c2ecf20Sopenharmony_ci return; 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci mutex_lock(&ghes_reg_mutex); 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci system_scanned = false; 6578c2ecf20Sopenharmony_ci memset(&ghes_hw, 0, sizeof(struct ghes_hw_desc)); 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci if (!refcount_dec_and_test(&ghes_refcount)) 6608c2ecf20Sopenharmony_ci goto unlock; 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci /* 6638c2ecf20Sopenharmony_ci * Wait for the irq handler being finished. 6648c2ecf20Sopenharmony_ci */ 6658c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_lock, flags); 6668c2ecf20Sopenharmony_ci mci = ghes_pvt ? ghes_pvt->mci : NULL; 6678c2ecf20Sopenharmony_ci ghes_pvt = NULL; 6688c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_lock, flags); 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_ci if (!mci) 6718c2ecf20Sopenharmony_ci goto unlock; 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_ci mci = edac_mc_del_mc(mci->pdev); 6748c2ecf20Sopenharmony_ci if (mci) 6758c2ecf20Sopenharmony_ci edac_mc_free(mci); 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_ciunlock: 6788c2ecf20Sopenharmony_ci mutex_unlock(&ghes_reg_mutex); 6798c2ecf20Sopenharmony_ci} 680