Commit | Line | Data |
---|---|---|
774fea1a SS |
1 | /* |
2 | * Error log support on PowerNV. | |
3 | * | |
4 | * Copyright 2013,2014 IBM Corp. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version | |
9 | * 2 of the License, or (at your option) any later version. | |
10 | */ | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/slab.h> | |
15 | #include <linux/sysfs.h> | |
16 | #include <linux/fs.h> | |
17 | #include <linux/vmalloc.h> | |
18 | #include <linux/fcntl.h> | |
19 | #include <linux/kobject.h> | |
20 | #include <asm/uaccess.h> | |
21 | #include <asm/opal.h> | |
22 | ||
23 | struct elog_obj { | |
24 | struct kobject kobj; | |
25 | struct bin_attribute raw_attr; | |
26 | uint64_t id; | |
27 | uint64_t type; | |
28 | size_t size; | |
29 | char *buffer; | |
30 | }; | |
31 | #define to_elog_obj(x) container_of(x, struct elog_obj, kobj) | |
32 | ||
33 | struct elog_attribute { | |
34 | struct attribute attr; | |
35 | ssize_t (*show)(struct elog_obj *elog, struct elog_attribute *attr, | |
36 | char *buf); | |
37 | ssize_t (*store)(struct elog_obj *elog, struct elog_attribute *attr, | |
38 | const char *buf, size_t count); | |
39 | }; | |
40 | #define to_elog_attr(x) container_of(x, struct elog_attribute, attr) | |
41 | ||
42 | static ssize_t elog_id_show(struct elog_obj *elog_obj, | |
43 | struct elog_attribute *attr, | |
44 | char *buf) | |
45 | { | |
46 | return sprintf(buf, "0x%llx\n", elog_obj->id); | |
47 | } | |
48 | ||
49 | static const char *elog_type_to_string(uint64_t type) | |
50 | { | |
51 | switch (type) { | |
52 | case 0: return "PEL"; | |
53 | default: return "unknown"; | |
54 | } | |
55 | } | |
56 | ||
57 | static ssize_t elog_type_show(struct elog_obj *elog_obj, | |
58 | struct elog_attribute *attr, | |
59 | char *buf) | |
60 | { | |
61 | return sprintf(buf, "0x%llx %s\n", | |
62 | elog_obj->type, | |
63 | elog_type_to_string(elog_obj->type)); | |
64 | } | |
65 | ||
66 | static ssize_t elog_ack_show(struct elog_obj *elog_obj, | |
67 | struct elog_attribute *attr, | |
68 | char *buf) | |
69 | { | |
70 | return sprintf(buf, "ack - acknowledge log message\n"); | |
71 | } | |
72 | ||
774fea1a SS |
73 | static ssize_t elog_ack_store(struct elog_obj *elog_obj, |
74 | struct elog_attribute *attr, | |
75 | const char *buf, | |
76 | size_t count) | |
77 | { | |
78 | opal_send_ack_elog(elog_obj->id); | |
cc4f265a SS |
79 | sysfs_remove_file_self(&elog_obj->kobj, &attr->attr); |
80 | kobject_put(&elog_obj->kobj); | |
774fea1a SS |
81 | return count; |
82 | } | |
83 | ||
84 | static struct elog_attribute id_attribute = | |
85 | __ATTR(id, 0666, elog_id_show, NULL); | |
86 | static struct elog_attribute type_attribute = | |
87 | __ATTR(type, 0666, elog_type_show, NULL); | |
88 | static struct elog_attribute ack_attribute = | |
89 | __ATTR(acknowledge, 0660, elog_ack_show, elog_ack_store); | |
90 | ||
91 | static struct kset *elog_kset; | |
92 | ||
93 | static ssize_t elog_attr_show(struct kobject *kobj, | |
94 | struct attribute *attr, | |
95 | char *buf) | |
96 | { | |
97 | struct elog_attribute *attribute; | |
98 | struct elog_obj *elog; | |
99 | ||
100 | attribute = to_elog_attr(attr); | |
101 | elog = to_elog_obj(kobj); | |
102 | ||
103 | if (!attribute->show) | |
104 | return -EIO; | |
105 | ||
106 | return attribute->show(elog, attribute, buf); | |
107 | } | |
108 | ||
109 | static ssize_t elog_attr_store(struct kobject *kobj, | |
110 | struct attribute *attr, | |
111 | const char *buf, size_t len) | |
112 | { | |
113 | struct elog_attribute *attribute; | |
114 | struct elog_obj *elog; | |
115 | ||
116 | attribute = to_elog_attr(attr); | |
117 | elog = to_elog_obj(kobj); | |
118 | ||
119 | if (!attribute->store) | |
120 | return -EIO; | |
121 | ||
122 | return attribute->store(elog, attribute, buf, len); | |
123 | } | |
124 | ||
125 | static const struct sysfs_ops elog_sysfs_ops = { | |
126 | .show = elog_attr_show, | |
127 | .store = elog_attr_store, | |
128 | }; | |
129 | ||
130 | static void elog_release(struct kobject *kobj) | |
131 | { | |
132 | struct elog_obj *elog; | |
133 | ||
134 | elog = to_elog_obj(kobj); | |
135 | kfree(elog->buffer); | |
136 | kfree(elog); | |
137 | } | |
138 | ||
139 | static struct attribute *elog_default_attrs[] = { | |
140 | &id_attribute.attr, | |
141 | &type_attribute.attr, | |
142 | &ack_attribute.attr, | |
143 | NULL, | |
144 | }; | |
145 | ||
146 | static struct kobj_type elog_ktype = { | |
147 | .sysfs_ops = &elog_sysfs_ops, | |
148 | .release = &elog_release, | |
149 | .default_attrs = elog_default_attrs, | |
150 | }; | |
151 | ||
152 | /* Maximum size of a single log on FSP is 16KB */ | |
153 | #define OPAL_MAX_ERRLOG_SIZE 16384 | |
154 | ||
155 | static ssize_t raw_attr_read(struct file *filep, struct kobject *kobj, | |
156 | struct bin_attribute *bin_attr, | |
157 | char *buffer, loff_t pos, size_t count) | |
158 | { | |
159 | int opal_rc; | |
160 | ||
161 | struct elog_obj *elog = to_elog_obj(kobj); | |
162 | ||
163 | /* We may have had an error reading before, so let's retry */ | |
164 | if (!elog->buffer) { | |
165 | elog->buffer = kzalloc(elog->size, GFP_KERNEL); | |
166 | if (!elog->buffer) | |
167 | return -EIO; | |
168 | ||
169 | opal_rc = opal_read_elog(__pa(elog->buffer), | |
170 | elog->size, elog->id); | |
171 | if (opal_rc != OPAL_SUCCESS) { | |
172 | pr_err("ELOG: log read failed for log-id=%llx\n", | |
173 | elog->id); | |
174 | kfree(elog->buffer); | |
175 | elog->buffer = NULL; | |
176 | return -EIO; | |
177 | } | |
178 | } | |
179 | ||
180 | memcpy(buffer, elog->buffer + pos, count); | |
181 | ||
182 | return count; | |
183 | } | |
184 | ||
185 | static struct elog_obj *create_elog_obj(uint64_t id, size_t size, uint64_t type) | |
186 | { | |
187 | struct elog_obj *elog; | |
188 | int rc; | |
189 | ||
190 | elog = kzalloc(sizeof(*elog), GFP_KERNEL); | |
191 | if (!elog) | |
192 | return NULL; | |
193 | ||
194 | elog->kobj.kset = elog_kset; | |
195 | ||
196 | kobject_init(&elog->kobj, &elog_ktype); | |
197 | ||
198 | sysfs_bin_attr_init(&elog->raw_attr); | |
199 | ||
200 | elog->raw_attr.attr.name = "raw"; | |
201 | elog->raw_attr.attr.mode = 0400; | |
202 | elog->raw_attr.size = size; | |
203 | elog->raw_attr.read = raw_attr_read; | |
204 | ||
205 | elog->id = id; | |
206 | elog->size = size; | |
207 | elog->type = type; | |
208 | ||
209 | elog->buffer = kzalloc(elog->size, GFP_KERNEL); | |
210 | ||
211 | if (elog->buffer) { | |
212 | rc = opal_read_elog(__pa(elog->buffer), | |
213 | elog->size, elog->id); | |
214 | if (rc != OPAL_SUCCESS) { | |
215 | pr_err("ELOG: log read failed for log-id=%llx\n", | |
216 | elog->id); | |
217 | kfree(elog->buffer); | |
218 | elog->buffer = NULL; | |
219 | } | |
220 | } | |
221 | ||
222 | rc = kobject_add(&elog->kobj, NULL, "0x%llx", id); | |
223 | if (rc) { | |
224 | kobject_put(&elog->kobj); | |
225 | return NULL; | |
226 | } | |
227 | ||
228 | rc = sysfs_create_bin_file(&elog->kobj, &elog->raw_attr); | |
229 | if (rc) { | |
230 | kobject_put(&elog->kobj); | |
231 | return NULL; | |
232 | } | |
233 | ||
234 | kobject_uevent(&elog->kobj, KOBJ_ADD); | |
235 | ||
236 | return elog; | |
237 | } | |
238 | ||
239 | static void elog_work_fn(struct work_struct *work) | |
240 | { | |
14ad0c58 AB |
241 | __be64 size; |
242 | __be64 id; | |
243 | __be64 type; | |
2bad7423 | 244 | uint64_t elog_size; |
774fea1a SS |
245 | uint64_t log_id; |
246 | uint64_t elog_type; | |
247 | int rc; | |
248 | char name[2+16+1]; | |
249 | ||
14ad0c58 | 250 | rc = opal_get_elog_size(&id, &size, &type); |
774fea1a | 251 | if (rc != OPAL_SUCCESS) { |
fa952c54 | 252 | pr_err("ELOG: OPAL log info read failed\n"); |
774fea1a SS |
253 | return; |
254 | } | |
255 | ||
14ad0c58 AB |
256 | elog_size = be64_to_cpu(size); |
257 | log_id = be64_to_cpu(id); | |
258 | elog_type = be64_to_cpu(type); | |
259 | ||
fa952c54 | 260 | WARN_ON(elog_size > OPAL_MAX_ERRLOG_SIZE); |
774fea1a SS |
261 | |
262 | if (elog_size >= OPAL_MAX_ERRLOG_SIZE) | |
263 | elog_size = OPAL_MAX_ERRLOG_SIZE; | |
264 | ||
265 | sprintf(name, "0x%llx", log_id); | |
266 | ||
267 | /* we may get notified twice, let's handle | |
268 | * that gracefully and not create two conflicting | |
269 | * entries. | |
270 | */ | |
271 | if (kset_find_obj(elog_kset, name)) | |
272 | return; | |
273 | ||
274 | create_elog_obj(log_id, elog_size, elog_type); | |
275 | } | |
276 | ||
277 | static DECLARE_WORK(elog_work, elog_work_fn); | |
278 | ||
279 | static int elog_event(struct notifier_block *nb, | |
280 | unsigned long events, void *change) | |
281 | { | |
282 | /* check for error log event */ | |
283 | if (events & OPAL_EVENT_ERROR_LOG_AVAIL) | |
284 | schedule_work(&elog_work); | |
285 | return 0; | |
286 | } | |
287 | ||
288 | static struct notifier_block elog_nb = { | |
289 | .notifier_call = elog_event, | |
290 | .next = NULL, | |
291 | .priority = 0 | |
292 | }; | |
293 | ||
294 | int __init opal_elog_init(void) | |
295 | { | |
296 | int rc = 0; | |
297 | ||
298 | elog_kset = kset_create_and_add("elog", NULL, opal_kobj); | |
299 | if (!elog_kset) { | |
300 | pr_warn("%s: failed to create elog kset\n", __func__); | |
301 | return -1; | |
302 | } | |
303 | ||
304 | rc = opal_notifier_register(&elog_nb); | |
305 | if (rc) { | |
306 | pr_err("%s: Can't register OPAL event notifier (%d)\n", | |
307 | __func__, rc); | |
308 | return rc; | |
309 | } | |
310 | ||
311 | /* We are now ready to pull error logs from opal. */ | |
312 | opal_resend_pending_logs(); | |
313 | ||
314 | return 0; | |
315 | } |