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. Use |
20 | * 2 pages to save MCE events for now (~80 MCE records at most). |
21 | */ |
22 | #define MCE_POOLSZ (2 * PAGE_SIZE) |
23 | |
24 | static struct gen_pool *mce_evt_pool; |
25 | static LLIST_HEAD(mce_event_llist); |
26 | static char gen_pool_buf[MCE_POOLSZ]; |
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_evt_llist *node; |
35 | struct mce *m1, *m2; |
36 | |
37 | m1 = &t->mce; |
38 | |
39 | llist_for_each_entry(node, &l->llnode, llnode) { |
40 | m2 = &node->mce; |
41 | |
42 | if (!mce_cmp(m1, m2)) |
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 llist_node *head; |
77 | struct mce_evt_llist *node, *tmp; |
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->mce; |
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 | int mce_gen_pool_add(struct mce *mce) |
98 | { |
99 | struct mce_evt_llist *node; |
100 | |
101 | if (filter_mce(m: mce)) |
102 | return -EINVAL; |
103 | |
104 | if (!mce_evt_pool) |
105 | return -EINVAL; |
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 -ENOMEM; |
111 | } |
112 | |
113 | memcpy(&node->mce, mce, sizeof(*mce)); |
114 | llist_add(new: &node->llnode, head: &mce_event_llist); |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | static int mce_gen_pool_create(void) |
120 | { |
121 | struct gen_pool *tmpp; |
122 | int ret = -ENOMEM; |
123 | |
124 | tmpp = gen_pool_create(ilog2(sizeof(struct mce_evt_llist)), -1); |
125 | if (!tmpp) |
126 | goto out; |
127 | |
128 | ret = gen_pool_add(pool: tmpp, addr: (unsigned long)gen_pool_buf, MCE_POOLSZ, nid: -1); |
129 | if (ret) { |
130 | gen_pool_destroy(tmpp); |
131 | goto out; |
132 | } |
133 | |
134 | mce_evt_pool = tmpp; |
135 | |
136 | out: |
137 | return ret; |
138 | } |
139 | |
140 | int mce_gen_pool_init(void) |
141 | { |
142 | /* Just init mce_gen_pool once. */ |
143 | if (mce_evt_pool) |
144 | return 0; |
145 | |
146 | return mce_gen_pool_create(); |
147 | } |
148 | |