linux/drivers/acpi/acpi_extlog.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Extended Error Log driver
   4 *
   5 * Copyright (C) 2013 Intel Corp.
   6 * Author: Chen, Gong <gong.chen@intel.com>
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/acpi.h>
  11#include <linux/cper.h>
  12#include <linux/ratelimit.h>
  13#include <linux/edac.h>
  14#include <linux/ras.h>
  15#include <asm/cpu.h>
  16#include <asm/mce.h>
  17
  18#include "apei/apei-internal.h"
  19#include <ras/ras_event.h>
  20
  21#define EXT_ELOG_ENTRY_MASK     GENMASK_ULL(51, 0) /* elog entry address mask */
  22
  23#define EXTLOG_DSM_REV          0x0
  24#define EXTLOG_FN_ADDR          0x1
  25
  26#define FLAG_OS_OPTIN           BIT(0)
  27#define ELOG_ENTRY_VALID        (1ULL<<63)
  28#define ELOG_ENTRY_LEN          0x1000
  29
  30#define EMCA_BUG \
  31        "Can not request iomem region <0x%016llx-0x%016llx> - eMCA disabled\n"
  32
  33struct extlog_l1_head {
  34        u32 ver;        /* Header Version */
  35        u32 hdr_len;    /* Header Length */
  36        u64 total_len;  /* entire L1 Directory length including this header */
  37        u64 elog_base;  /* MCA Error Log Directory base address */
  38        u64 elog_len;   /* MCA Error Log Directory length */
  39        u32 flags;      /* bit 0 - OS/VMM Opt-in */
  40        u8  rev0[12];
  41        u32 entries;    /* Valid L1 Directory entries per logical processor */
  42        u8  rev1[12];
  43};
  44
  45static u8 extlog_dsm_uuid[] __initdata = "663E35AF-CC10-41A4-88EA-5470AF055295";
  46
  47/* L1 table related physical address */
  48static u64 elog_base;
  49static size_t elog_size;
  50static u64 l1_dirbase;
  51static size_t l1_size;
  52
  53/* L1 table related virtual address */
  54static void __iomem *extlog_l1_addr;
  55static void __iomem *elog_addr;
  56
  57static void *elog_buf;
  58
  59static u64 *l1_entry_base;
  60static u32 l1_percpu_entry;
  61
  62#define ELOG_IDX(cpu, bank) \
  63        (cpu_physical_id(cpu) * l1_percpu_entry + (bank))
  64
  65#define ELOG_ENTRY_DATA(idx) \
  66        (*(l1_entry_base + (idx)))
  67
  68#define ELOG_ENTRY_ADDR(phyaddr) \
  69        (phyaddr - elog_base + (u8 *)elog_addr)
  70
  71static struct acpi_hest_generic_status *extlog_elog_entry_check(int cpu, int bank)
  72{
  73        int idx;
  74        u64 data;
  75        struct acpi_hest_generic_status *estatus;
  76
  77        WARN_ON(cpu < 0);
  78        idx = ELOG_IDX(cpu, bank);
  79        data = ELOG_ENTRY_DATA(idx);
  80        if ((data & ELOG_ENTRY_VALID) == 0)
  81                return NULL;
  82
  83        data &= EXT_ELOG_ENTRY_MASK;
  84        estatus = (struct acpi_hest_generic_status *)ELOG_ENTRY_ADDR(data);
  85
  86        /* if no valid data in elog entry, just return */
  87        if (estatus->block_status == 0)
  88                return NULL;
  89
  90        return estatus;
  91}
  92
  93static void __print_extlog_rcd(const char *pfx,
  94                               struct acpi_hest_generic_status *estatus, int cpu)
  95{
  96        static atomic_t seqno;
  97        unsigned int curr_seqno;
  98        char pfx_seq[64];
  99
 100        if (!pfx) {
 101                if (estatus->error_severity <= CPER_SEV_CORRECTED)
 102                        pfx = KERN_INFO;
 103                else
 104                        pfx = KERN_ERR;
 105        }
 106        curr_seqno = atomic_inc_return(&seqno);
 107        snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}", pfx, curr_seqno);
 108        printk("%s""Hardware error detected on CPU%d\n", pfx_seq, cpu);
 109        cper_estatus_print(pfx_seq, estatus);
 110}
 111
 112static int print_extlog_rcd(const char *pfx,
 113                            struct acpi_hest_generic_status *estatus, int cpu)
 114{
 115        /* Not more than 2 messages every 5 seconds */
 116        static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2);
 117        static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5*HZ, 2);
 118        struct ratelimit_state *ratelimit;
 119
 120        if (estatus->error_severity == CPER_SEV_CORRECTED ||
 121            (estatus->error_severity == CPER_SEV_INFORMATIONAL))
 122                ratelimit = &ratelimit_corrected;
 123        else
 124                ratelimit = &ratelimit_uncorrected;
 125        if (__ratelimit(ratelimit)) {
 126                __print_extlog_rcd(pfx, estatus, cpu);
 127                return 0;
 128        }
 129
 130        return 1;
 131}
 132
 133static int extlog_print(struct notifier_block *nb, unsigned long val,
 134                        void *data)
 135{
 136        struct mce *mce = (struct mce *)data;
 137        int     bank = mce->bank;
 138        int     cpu = mce->extcpu;
 139        struct acpi_hest_generic_status *estatus, *tmp;
 140        struct acpi_hest_generic_data *gdata;
 141        const guid_t *fru_id = &guid_null;
 142        char *fru_text = "";
 143        guid_t *sec_type;
 144        static u32 err_seq;
 145
 146        estatus = extlog_elog_entry_check(cpu, bank);
 147        if (estatus == NULL || (mce->kflags & MCE_HANDLED_CEC))
 148                return NOTIFY_DONE;
 149
 150        memcpy(elog_buf, (void *)estatus, ELOG_ENTRY_LEN);
 151        /* clear record status to enable BIOS to update it again */
 152        estatus->block_status = 0;
 153
 154        tmp = (struct acpi_hest_generic_status *)elog_buf;
 155
 156        if (!ras_userspace_consumers()) {
 157                print_extlog_rcd(NULL, tmp, cpu);
 158                goto out;
 159        }
 160
 161        /* log event via trace */
 162        err_seq++;
 163        gdata = (struct acpi_hest_generic_data *)(tmp + 1);
 164        if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
 165                fru_id = (guid_t *)gdata->fru_id;
 166        if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
 167                fru_text = gdata->fru_text;
 168        sec_type = (guid_t *)gdata->section_type;
 169        if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
 170                struct cper_sec_mem_err *mem = (void *)(gdata + 1);
 171                if (gdata->error_data_length >= sizeof(*mem))
 172                        trace_extlog_mem_event(mem, err_seq, fru_id, fru_text,
 173                                               (u8)gdata->error_severity);
 174        }
 175
 176out:
 177        mce->kflags |= MCE_HANDLED_EXTLOG;
 178        return NOTIFY_OK;
 179}
 180
 181static bool __init extlog_get_l1addr(void)
 182{
 183        guid_t guid;
 184        acpi_handle handle;
 185        union acpi_object *obj;
 186
 187        if (guid_parse(extlog_dsm_uuid, &guid))
 188                return false;
 189        if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle)))
 190                return false;
 191        if (!acpi_check_dsm(handle, &guid, EXTLOG_DSM_REV, 1 << EXTLOG_FN_ADDR))
 192                return false;
 193        obj = acpi_evaluate_dsm_typed(handle, &guid, EXTLOG_DSM_REV,
 194                                      EXTLOG_FN_ADDR, NULL, ACPI_TYPE_INTEGER);
 195        if (!obj) {
 196                return false;
 197        } else {
 198                l1_dirbase = obj->integer.value;
 199                ACPI_FREE(obj);
 200        }
 201
 202        /* Spec says L1 directory must be 4K aligned, bail out if it isn't */
 203        if (l1_dirbase & ((1 << 12) - 1)) {
 204                pr_warn(FW_BUG "L1 Directory is invalid at physical %llx\n",
 205                        l1_dirbase);
 206                return false;
 207        }
 208
 209        return true;
 210}
 211static struct notifier_block extlog_mce_dec = {
 212        .notifier_call  = extlog_print,
 213        .priority       = MCE_PRIO_EXTLOG,
 214};
 215
 216static int __init extlog_init(void)
 217{
 218        struct extlog_l1_head *l1_head;
 219        void __iomem *extlog_l1_hdr;
 220        size_t l1_hdr_size;
 221        struct resource *r;
 222        u64 cap;
 223        int rc;
 224
 225        if (rdmsrl_safe(MSR_IA32_MCG_CAP, &cap) ||
 226            !(cap & MCG_ELOG_P) ||
 227            !extlog_get_l1addr())
 228                return -ENODEV;
 229
 230        rc = -EINVAL;
 231        /* get L1 header to fetch necessary information */
 232        l1_hdr_size = sizeof(struct extlog_l1_head);
 233        r = request_mem_region(l1_dirbase, l1_hdr_size, "L1 DIR HDR");
 234        if (!r) {
 235                pr_warn(FW_BUG EMCA_BUG,
 236                        (unsigned long long)l1_dirbase,
 237                        (unsigned long long)l1_dirbase + l1_hdr_size);
 238                goto err;
 239        }
 240
 241        extlog_l1_hdr = acpi_os_map_iomem(l1_dirbase, l1_hdr_size);
 242        l1_head = (struct extlog_l1_head *)extlog_l1_hdr;
 243        l1_size = l1_head->total_len;
 244        l1_percpu_entry = l1_head->entries;
 245        elog_base = l1_head->elog_base;
 246        elog_size = l1_head->elog_len;
 247        acpi_os_unmap_iomem(extlog_l1_hdr, l1_hdr_size);
 248        release_mem_region(l1_dirbase, l1_hdr_size);
 249
 250        /* remap L1 header again based on completed information */
 251        r = request_mem_region(l1_dirbase, l1_size, "L1 Table");
 252        if (!r) {
 253                pr_warn(FW_BUG EMCA_BUG,
 254                        (unsigned long long)l1_dirbase,
 255                        (unsigned long long)l1_dirbase + l1_size);
 256                goto err;
 257        }
 258        extlog_l1_addr = acpi_os_map_iomem(l1_dirbase, l1_size);
 259        l1_entry_base = (u64 *)((u8 *)extlog_l1_addr + l1_hdr_size);
 260
 261        /* remap elog table */
 262        r = request_mem_region(elog_base, elog_size, "Elog Table");
 263        if (!r) {
 264                pr_warn(FW_BUG EMCA_BUG,
 265                        (unsigned long long)elog_base,
 266                        (unsigned long long)elog_base + elog_size);
 267                goto err_release_l1_dir;
 268        }
 269        elog_addr = acpi_os_map_iomem(elog_base, elog_size);
 270
 271        rc = -ENOMEM;
 272        /* allocate buffer to save elog record */
 273        elog_buf = kmalloc(ELOG_ENTRY_LEN, GFP_KERNEL);
 274        if (elog_buf == NULL)
 275                goto err_release_elog;
 276
 277        mce_register_decode_chain(&extlog_mce_dec);
 278        /* enable OS to be involved to take over management from BIOS */
 279        ((struct extlog_l1_head *)extlog_l1_addr)->flags |= FLAG_OS_OPTIN;
 280
 281        return 0;
 282
 283err_release_elog:
 284        if (elog_addr)
 285                acpi_os_unmap_iomem(elog_addr, elog_size);
 286        release_mem_region(elog_base, elog_size);
 287err_release_l1_dir:
 288        if (extlog_l1_addr)
 289                acpi_os_unmap_iomem(extlog_l1_addr, l1_size);
 290        release_mem_region(l1_dirbase, l1_size);
 291err:
 292        pr_warn(FW_BUG "Extended error log disabled because of problems parsing f/w tables\n");
 293        return rc;
 294}
 295
 296static void __exit extlog_exit(void)
 297{
 298        mce_unregister_decode_chain(&extlog_mce_dec);
 299        ((struct extlog_l1_head *)extlog_l1_addr)->flags &= ~FLAG_OS_OPTIN;
 300        if (extlog_l1_addr)
 301                acpi_os_unmap_iomem(extlog_l1_addr, l1_size);
 302        if (elog_addr)
 303                acpi_os_unmap_iomem(elog_addr, elog_size);
 304        release_mem_region(elog_base, elog_size);
 305        release_mem_region(l1_dirbase, l1_size);
 306        kfree(elog_buf);
 307}
 308
 309module_init(extlog_init);
 310module_exit(extlog_exit);
 311
 312MODULE_AUTHOR("Chen, Gong <gong.chen@intel.com>");
 313MODULE_DESCRIPTION("Extended MCA Error Log Driver");
 314MODULE_LICENSE("GPL");
 315