Commit | Line | Data |
---|---|---|
648ed940 CG |
1 | /* |
2 | * MCE event pool management in MCE context | |
3 | * | |
4 | * Copyright (C) 2015 Intel Corp. | |
5 | * Author: Chen, Gong <gong.chen@linux.intel.com> | |
6 | * | |
7 | * This file is licensed under GPLv2. | |
8 | */ | |
9 | #include <linux/smp.h> | |
10 | #include <linux/mm.h> | |
11 | #include <linux/genalloc.h> | |
12 | #include <linux/llist.h> | |
13 | #include "mce-internal.h" | |
14 | ||
15 | /* | |
16 | * printk() is not safe in MCE context. This is a lock-less memory allocator | |
17 | * used to save error information organized in a lock-less list. | |
18 | * | |
19 | * This memory pool is only to be used to save MCE records in MCE context. | |
20 | * MCE events are rare, so a fixed size memory pool should be enough. Use | |
21 | * 2 pages to save MCE events for now (~80 MCE records at most). | |
22 | */ | |
23 | #define MCE_POOLSZ (2 * PAGE_SIZE) | |
24 | ||
25 | static struct gen_pool *mce_evt_pool; | |
26 | static LLIST_HEAD(mce_event_llist); | |
27 | static char gen_pool_buf[MCE_POOLSZ]; | |
28 | ||
5541c93c TL |
29 | /* |
30 | * Compare the record "t" with each of the records on list "l" to see if | |
31 | * an equivalent one is present in the list. | |
32 | */ | |
33 | static bool is_duplicate_mce_record(struct mce_evt_llist *t, struct mce_evt_llist *l) | |
34 | { | |
35 | struct mce_evt_llist *node; | |
36 | struct mce *m1, *m2; | |
37 | ||
38 | m1 = &t->mce; | |
39 | ||
40 | llist_for_each_entry(node, &l->llnode, llnode) { | |
41 | m2 = &node->mce; | |
42 | ||
43 | if (!mce_cmp(m1, m2)) | |
44 | return true; | |
45 | } | |
46 | return false; | |
47 | } | |
48 | ||
49 | /* | |
50 | * The system has panicked - we'd like to peruse the list of MCE records | |
51 | * that have been queued, but not seen by anyone yet. The list is in | |
52 | * reverse time order, so we need to reverse it. While doing that we can | |
53 | * also drop duplicate records (these were logged because some banks are | |
54 | * shared between cores or by all threads on a socket). | |
55 | */ | |
56 | struct llist_node *mce_gen_pool_prepare_records(void) | |
57 | { | |
58 | struct llist_node *head; | |
59 | LLIST_HEAD(new_head); | |
60 | struct mce_evt_llist *node, *t; | |
61 | ||
62 | head = llist_del_all(&mce_event_llist); | |
63 | if (!head) | |
64 | return NULL; | |
65 | ||
66 | /* squeeze out duplicates while reversing order */ | |
67 | llist_for_each_entry_safe(node, t, head, llnode) { | |
68 | if (!is_duplicate_mce_record(node, t)) | |
69 | llist_add(&node->llnode, &new_head); | |
70 | } | |
71 | ||
72 | return new_head.first; | |
73 | } | |
74 | ||
648ed940 CG |
75 | void mce_gen_pool_process(void) |
76 | { | |
77 | struct llist_node *head; | |
a3125494 | 78 | struct mce_evt_llist *node, *tmp; |
648ed940 CG |
79 | struct mce *mce; |
80 | ||
81 | head = llist_del_all(&mce_event_llist); | |
82 | if (!head) | |
83 | return; | |
84 | ||
85 | head = llist_reverse_order(head); | |
a3125494 | 86 | llist_for_each_entry_safe(node, tmp, head, llnode) { |
648ed940 CG |
87 | mce = &node->mce; |
88 | atomic_notifier_call_chain(&x86_mce_decoder_chain, 0, mce); | |
89 | gen_pool_free(mce_evt_pool, (unsigned long)node, sizeof(*node)); | |
90 | } | |
91 | } | |
92 | ||
93 | bool mce_gen_pool_empty(void) | |
94 | { | |
95 | return llist_empty(&mce_event_llist); | |
96 | } | |
97 | ||
98 | int mce_gen_pool_add(struct mce *mce) | |
99 | { | |
100 | struct mce_evt_llist *node; | |
101 | ||
102 | if (!mce_evt_pool) | |
103 | return -EINVAL; | |
104 | ||
105 | node = (void *)gen_pool_alloc(mce_evt_pool, sizeof(*node)); | |
106 | if (!node) { | |
107 | pr_warn_ratelimited("MCE records pool full!\n"); | |
108 | return -ENOMEM; | |
109 | } | |
110 | ||
111 | memcpy(&node->mce, mce, sizeof(*mce)); | |
112 | llist_add(&node->llnode, &mce_event_llist); | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | static int mce_gen_pool_create(void) | |
118 | { | |
119 | struct gen_pool *tmpp; | |
120 | int ret = -ENOMEM; | |
121 | ||
122 | tmpp = gen_pool_create(ilog2(sizeof(struct mce_evt_llist)), -1); | |
123 | if (!tmpp) | |
124 | goto out; | |
125 | ||
126 | ret = gen_pool_add(tmpp, (unsigned long)gen_pool_buf, MCE_POOLSZ, -1); | |
127 | if (ret) { | |
128 | gen_pool_destroy(tmpp); | |
129 | goto out; | |
130 | } | |
131 | ||
132 | mce_evt_pool = tmpp; | |
133 | ||
134 | out: | |
135 | return ret; | |
136 | } | |
137 | ||
138 | int mce_gen_pool_init(void) | |
139 | { | |
140 | /* Just init mce_gen_pool once. */ | |
141 | if (mce_evt_pool) | |
142 | return 0; | |
143 | ||
144 | return mce_gen_pool_create(); | |
145 | } |