| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * MCE event pool management in MCE context |
| 4 | * |
| 5 | * Copyright (C) 2015 Intel Corp. |
| 6 | * Author: Chen, Gong <gong.chen@linux.intel.com> |
| 7 | */ |
| 8 | #include <linux/smp.h> |
| 9 | #include <linux/mm.h> |
| 10 | #include <linux/genalloc.h> |
| 11 | #include <linux/llist.h> |
| 12 | #include "internal.h" |
| 13 | |
| 14 | /* |
| 15 | * printk() is not safe in MCE context. This is a lock-less memory allocator |
| 16 | * used to save error information organized in a lock-less list. |
| 17 | * |
| 18 | * This memory pool is only to be used to save MCE records in MCE context. |
| 19 | * MCE events are rare, so a fixed size memory pool should be enough. |
| 20 | * Allocate on a sliding scale based on number of CPUs. |
| 21 | */ |
| 22 | #define MCE_MIN_ENTRIES 80 |
| 23 | #define MCE_PER_CPU 2 |
| 24 | |
| 25 | static struct gen_pool *mce_evt_pool; |
| 26 | static LLIST_HEAD(mce_event_llist); |
| 27 | |
| 28 | /* |
| 29 | * Compare the record "t" with each of the records on list "l" to see if |
| 30 | * an equivalent one is present in the list. |
| 31 | */ |
| 32 | static bool is_duplicate_mce_record(struct mce_evt_llist *t, struct mce_evt_llist *l) |
| 33 | { |
| 34 | struct mce_hw_err *err1, *err2; |
| 35 | struct mce_evt_llist *node; |
| 36 | |
| 37 | err1 = &t->err; |
| 38 | |
| 39 | llist_for_each_entry(node, &l->llnode, llnode) { |
| 40 | err2 = &node->err; |
| 41 | |
| 42 | if (!mce_cmp(m1: &err1->m, m2: &err2->m)) |
| 43 | return true; |
| 44 | } |
| 45 | return false; |
| 46 | } |
| 47 | |
| 48 | /* |
| 49 | * The system has panicked - we'd like to peruse the list of MCE records |
| 50 | * that have been queued, but not seen by anyone yet. The list is in |
| 51 | * reverse time order, so we need to reverse it. While doing that we can |
| 52 | * also drop duplicate records (these were logged because some banks are |
| 53 | * shared between cores or by all threads on a socket). |
| 54 | */ |
| 55 | struct llist_node *mce_gen_pool_prepare_records(void) |
| 56 | { |
| 57 | struct llist_node *head; |
| 58 | LLIST_HEAD(new_head); |
| 59 | struct mce_evt_llist *node, *t; |
| 60 | |
| 61 | head = llist_del_all(head: &mce_event_llist); |
| 62 | if (!head) |
| 63 | return NULL; |
| 64 | |
| 65 | /* squeeze out duplicates while reversing order */ |
| 66 | llist_for_each_entry_safe(node, t, head, llnode) { |
| 67 | if (!is_duplicate_mce_record(t: node, l: t)) |
| 68 | llist_add(new: &node->llnode, head: &new_head); |
| 69 | } |
| 70 | |
| 71 | return new_head.first; |
| 72 | } |
| 73 | |
| 74 | void mce_gen_pool_process(struct work_struct *__unused) |
| 75 | { |
| 76 | struct mce_evt_llist *node, *tmp; |
| 77 | struct llist_node *head; |
| 78 | struct mce *mce; |
| 79 | |
| 80 | head = llist_del_all(head: &mce_event_llist); |
| 81 | if (!head) |
| 82 | return; |
| 83 | |
| 84 | head = llist_reverse_order(head); |
| 85 | llist_for_each_entry_safe(node, tmp, head, llnode) { |
| 86 | mce = &node->err.m; |
| 87 | blocking_notifier_call_chain(nh: &x86_mce_decoder_chain, val: 0, v: mce); |
| 88 | gen_pool_free(pool: mce_evt_pool, addr: (unsigned long)node, size: sizeof(*node)); |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | bool mce_gen_pool_empty(void) |
| 93 | { |
| 94 | return llist_empty(head: &mce_event_llist); |
| 95 | } |
| 96 | |
| 97 | bool mce_gen_pool_add(struct mce_hw_err *err) |
| 98 | { |
| 99 | struct mce_evt_llist *node; |
| 100 | |
| 101 | if (filter_mce(m: &err->m)) |
| 102 | return false; |
| 103 | |
| 104 | if (!mce_evt_pool) |
| 105 | return false; |
| 106 | |
| 107 | node = (void *)gen_pool_alloc(pool: mce_evt_pool, size: sizeof(*node)); |
| 108 | if (!node) { |
| 109 | pr_warn_ratelimited("MCE records pool full!\n" ); |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | memcpy(&node->err, err, sizeof(*err)); |
| 114 | llist_add(new: &node->llnode, head: &mce_event_llist); |
| 115 | |
| 116 | return true; |
| 117 | } |
| 118 | |
| 119 | static bool mce_gen_pool_create(void) |
| 120 | { |
| 121 | int mce_numrecords, mce_poolsz, order; |
| 122 | struct gen_pool *gpool; |
| 123 | void *mce_pool; |
| 124 | |
| 125 | order = order_base_2(sizeof(struct mce_evt_llist)); |
| 126 | gpool = gen_pool_create(order, -1); |
| 127 | if (!gpool) |
| 128 | return false; |
| 129 | |
| 130 | mce_numrecords = max(MCE_MIN_ENTRIES, num_possible_cpus() * MCE_PER_CPU); |
| 131 | mce_poolsz = mce_numrecords * (1 << order); |
| 132 | mce_pool = kmalloc(mce_poolsz, GFP_KERNEL); |
| 133 | if (!mce_pool) { |
| 134 | gen_pool_destroy(gpool); |
| 135 | return false; |
| 136 | } |
| 137 | |
| 138 | if (gen_pool_add(pool: gpool, addr: (unsigned long)mce_pool, size: mce_poolsz, nid: -1)) { |
| 139 | gen_pool_destroy(gpool); |
| 140 | kfree(objp: mce_pool); |
| 141 | return false; |
| 142 | } |
| 143 | |
| 144 | mce_evt_pool = gpool; |
| 145 | |
| 146 | return true; |
| 147 | } |
| 148 | |
| 149 | bool mce_gen_pool_init(void) |
| 150 | { |
| 151 | /* Just init mce_gen_pool once. */ |
| 152 | if (mce_evt_pool) |
| 153 | return true; |
| 154 | |
| 155 | return mce_gen_pool_create(); |
| 156 | } |
| 157 | |