]>
Commit | Line | Data |
---|---|---|
c672528a CC |
1 | /* |
2 | * Copyright (C) 2012 Google, Inc. | |
3 | * | |
4 | * This software is licensed under the terms of the GNU General Public | |
5 | * License version 2, as published by the Free Software Foundation, and | |
6 | * may be copied, distributed, and modified under those terms. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | */ | |
14 | ||
404a6043 CC |
15 | #include <linux/device.h> |
16 | #include <linux/err.h> | |
c672528a CC |
17 | #include <linux/errno.h> |
18 | #include <linux/kernel.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/io.h> | |
404a6043 CC |
21 | #include <linux/list.h> |
22 | #include <linux/memblock.h> | |
9cc05ad9 | 23 | #include <linux/rslib.h> |
c672528a | 24 | #include <linux/slab.h> |
404a6043 | 25 | #include <linux/vmalloc.h> |
cddb8751 | 26 | #include <linux/pstore_ram.h> |
24c3d2f3 | 27 | #include <asm/page.h> |
c672528a | 28 | |
c672528a CC |
29 | struct persistent_ram_buffer { |
30 | uint32_t sig; | |
808d0387 CC |
31 | atomic_t start; |
32 | atomic_t size; | |
c672528a CC |
33 | uint8_t data[0]; |
34 | }; | |
35 | ||
36 | #define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */ | |
37 | ||
404a6043 | 38 | static __initdata LIST_HEAD(persistent_ram_list); |
c672528a | 39 | |
808d0387 CC |
40 | static inline size_t buffer_size(struct persistent_ram_zone *prz) |
41 | { | |
42 | return atomic_read(&prz->buffer->size); | |
43 | } | |
44 | ||
45 | static inline size_t buffer_start(struct persistent_ram_zone *prz) | |
46 | { | |
47 | return atomic_read(&prz->buffer->start); | |
48 | } | |
49 | ||
50 | /* increase and wrap the start pointer, returning the old value */ | |
51 | static inline size_t buffer_start_add(struct persistent_ram_zone *prz, size_t a) | |
52 | { | |
53 | int old; | |
54 | int new; | |
55 | ||
56 | do { | |
57 | old = atomic_read(&prz->buffer->start); | |
58 | new = old + a; | |
59 | while (unlikely(new > prz->buffer_size)) | |
60 | new -= prz->buffer_size; | |
61 | } while (atomic_cmpxchg(&prz->buffer->start, old, new) != old); | |
62 | ||
63 | return old; | |
64 | } | |
65 | ||
66 | /* increase the size counter until it hits the max size */ | |
67 | static inline void buffer_size_add(struct persistent_ram_zone *prz, size_t a) | |
68 | { | |
69 | size_t old; | |
70 | size_t new; | |
71 | ||
72 | if (atomic_read(&prz->buffer->size) == prz->buffer_size) | |
73 | return; | |
74 | ||
75 | do { | |
76 | old = atomic_read(&prz->buffer->size); | |
77 | new = old + a; | |
78 | if (new > prz->buffer_size) | |
79 | new = prz->buffer_size; | |
80 | } while (atomic_cmpxchg(&prz->buffer->size, old, new) != old); | |
81 | } | |
82 | ||
a15d0b36 | 83 | static void notrace persistent_ram_encode_rs8(struct persistent_ram_zone *prz, |
c672528a CC |
84 | uint8_t *data, size_t len, uint8_t *ecc) |
85 | { | |
86 | int i; | |
9cc05ad9 CC |
87 | uint16_t par[prz->ecc_size]; |
88 | ||
c672528a CC |
89 | /* Initialize the parity buffer */ |
90 | memset(par, 0, sizeof(par)); | |
91 | encode_rs8(prz->rs_decoder, data, len, par, 0); | |
9cc05ad9 | 92 | for (i = 0; i < prz->ecc_size; i++) |
c672528a CC |
93 | ecc[i] = par[i]; |
94 | } | |
95 | ||
96 | static int persistent_ram_decode_rs8(struct persistent_ram_zone *prz, | |
97 | void *data, size_t len, uint8_t *ecc) | |
98 | { | |
99 | int i; | |
9cc05ad9 CC |
100 | uint16_t par[prz->ecc_size]; |
101 | ||
102 | for (i = 0; i < prz->ecc_size; i++) | |
c672528a CC |
103 | par[i] = ecc[i]; |
104 | return decode_rs8(prz->rs_decoder, data, par, len, | |
105 | NULL, 0, NULL, 0, NULL); | |
106 | } | |
c672528a | 107 | |
a15d0b36 | 108 | static void notrace persistent_ram_update_ecc(struct persistent_ram_zone *prz, |
808d0387 | 109 | unsigned int start, unsigned int count) |
c672528a CC |
110 | { |
111 | struct persistent_ram_buffer *buffer = prz->buffer; | |
c672528a CC |
112 | uint8_t *buffer_end = buffer->data + prz->buffer_size; |
113 | uint8_t *block; | |
114 | uint8_t *par; | |
9cc05ad9 CC |
115 | int ecc_block_size = prz->ecc_block_size; |
116 | int ecc_size = prz->ecc_size; | |
117 | int size = prz->ecc_block_size; | |
118 | ||
119 | if (!prz->ecc) | |
120 | return; | |
121 | ||
808d0387 CC |
122 | block = buffer->data + (start & ~(ecc_block_size - 1)); |
123 | par = prz->par_buffer + (start / ecc_block_size) * prz->ecc_size; | |
124 | ||
c672528a | 125 | do { |
9cc05ad9 | 126 | if (block + ecc_block_size > buffer_end) |
c672528a CC |
127 | size = buffer_end - block; |
128 | persistent_ram_encode_rs8(prz, block, size, par); | |
9cc05ad9 CC |
129 | block += ecc_block_size; |
130 | par += ecc_size; | |
808d0387 | 131 | } while (block < buffer->data + start + count); |
c672528a CC |
132 | } |
133 | ||
9cc05ad9 | 134 | static void persistent_ram_update_header_ecc(struct persistent_ram_zone *prz) |
c672528a | 135 | { |
c672528a CC |
136 | struct persistent_ram_buffer *buffer = prz->buffer; |
137 | ||
9cc05ad9 CC |
138 | if (!prz->ecc) |
139 | return; | |
140 | ||
c672528a CC |
141 | persistent_ram_encode_rs8(prz, (uint8_t *)buffer, sizeof(*buffer), |
142 | prz->par_header); | |
c672528a CC |
143 | } |
144 | ||
9cc05ad9 | 145 | static void persistent_ram_ecc_old(struct persistent_ram_zone *prz) |
c672528a CC |
146 | { |
147 | struct persistent_ram_buffer *buffer = prz->buffer; | |
c672528a CC |
148 | uint8_t *block; |
149 | uint8_t *par; | |
150 | ||
9cc05ad9 CC |
151 | if (!prz->ecc) |
152 | return; | |
153 | ||
c672528a CC |
154 | block = buffer->data; |
155 | par = prz->par_buffer; | |
808d0387 | 156 | while (block < buffer->data + buffer_size(prz)) { |
c672528a | 157 | int numerr; |
9cc05ad9 | 158 | int size = prz->ecc_block_size; |
c672528a CC |
159 | if (block + size > buffer->data + prz->buffer_size) |
160 | size = buffer->data + prz->buffer_size - block; | |
161 | numerr = persistent_ram_decode_rs8(prz, block, size, par); | |
162 | if (numerr > 0) { | |
9cc05ad9 | 163 | pr_devel("persistent_ram: error in block %p, %d\n", |
c672528a | 164 | block, numerr); |
c672528a CC |
165 | prz->corrected_bytes += numerr; |
166 | } else if (numerr < 0) { | |
9cc05ad9 | 167 | pr_devel("persistent_ram: uncorrectable error in block %p\n", |
c672528a | 168 | block); |
c672528a CC |
169 | prz->bad_blocks++; |
170 | } | |
9cc05ad9 CC |
171 | block += prz->ecc_block_size; |
172 | par += prz->ecc_size; | |
173 | } | |
174 | } | |
175 | ||
176 | static int persistent_ram_init_ecc(struct persistent_ram_zone *prz, | |
177 | size_t buffer_size) | |
178 | { | |
179 | int numerr; | |
180 | struct persistent_ram_buffer *buffer = prz->buffer; | |
181 | int ecc_blocks; | |
182 | ||
183 | if (!prz->ecc) | |
184 | return 0; | |
185 | ||
186 | prz->ecc_block_size = 128; | |
187 | prz->ecc_size = 16; | |
188 | prz->ecc_symsize = 8; | |
189 | prz->ecc_poly = 0x11d; | |
190 | ||
191 | ecc_blocks = DIV_ROUND_UP(prz->buffer_size, prz->ecc_block_size); | |
192 | prz->buffer_size -= (ecc_blocks + 1) * prz->ecc_size; | |
193 | ||
194 | if (prz->buffer_size > buffer_size) { | |
195 | pr_err("persistent_ram: invalid size %zu, non-ecc datasize %zu\n", | |
196 | buffer_size, prz->buffer_size); | |
197 | return -EINVAL; | |
198 | } | |
199 | ||
200 | prz->par_buffer = buffer->data + prz->buffer_size; | |
201 | prz->par_header = prz->par_buffer + ecc_blocks * prz->ecc_size; | |
202 | ||
203 | /* | |
204 | * first consecutive root is 0 | |
205 | * primitive element to generate roots = 1 | |
206 | */ | |
207 | prz->rs_decoder = init_rs(prz->ecc_symsize, prz->ecc_poly, 0, 1, | |
208 | prz->ecc_size); | |
209 | if (prz->rs_decoder == NULL) { | |
210 | pr_info("persistent_ram: init_rs failed\n"); | |
211 | return -EINVAL; | |
c672528a | 212 | } |
9cc05ad9 CC |
213 | |
214 | prz->corrected_bytes = 0; | |
215 | prz->bad_blocks = 0; | |
216 | ||
217 | numerr = persistent_ram_decode_rs8(prz, buffer, sizeof(*buffer), | |
218 | prz->par_header); | |
219 | if (numerr > 0) { | |
220 | pr_info("persistent_ram: error in header, %d\n", numerr); | |
221 | prz->corrected_bytes += numerr; | |
222 | } else if (numerr < 0) { | |
223 | pr_info("persistent_ram: uncorrectable error in header\n"); | |
224 | prz->bad_blocks++; | |
225 | } | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz, | |
231 | char *str, size_t len) | |
232 | { | |
233 | ssize_t ret; | |
234 | ||
235 | if (prz->corrected_bytes || prz->bad_blocks) | |
236 | ret = snprintf(str, len, "" | |
237 | "\n%d Corrected bytes, %d unrecoverable blocks\n", | |
238 | prz->corrected_bytes, prz->bad_blocks); | |
239 | else | |
240 | ret = snprintf(str, len, "\nNo errors detected\n"); | |
241 | ||
242 | return ret; | |
243 | } | |
244 | ||
a15d0b36 | 245 | static void notrace persistent_ram_update(struct persistent_ram_zone *prz, |
808d0387 | 246 | const void *s, unsigned int start, unsigned int count) |
9cc05ad9 CC |
247 | { |
248 | struct persistent_ram_buffer *buffer = prz->buffer; | |
808d0387 CC |
249 | memcpy(buffer->data + start, s, count); |
250 | persistent_ram_update_ecc(prz, start, count); | |
9cc05ad9 CC |
251 | } |
252 | ||
253 | static void __init | |
254 | persistent_ram_save_old(struct persistent_ram_zone *prz) | |
255 | { | |
256 | struct persistent_ram_buffer *buffer = prz->buffer; | |
808d0387 CC |
257 | size_t size = buffer_size(prz); |
258 | size_t start = buffer_start(prz); | |
9cc05ad9 CC |
259 | char *dest; |
260 | ||
261 | persistent_ram_ecc_old(prz); | |
c672528a | 262 | |
808d0387 | 263 | dest = kmalloc(size, GFP_KERNEL); |
c672528a CC |
264 | if (dest == NULL) { |
265 | pr_err("persistent_ram: failed to allocate buffer\n"); | |
266 | return; | |
267 | } | |
268 | ||
269 | prz->old_log = dest; | |
808d0387 CC |
270 | prz->old_log_size = size; |
271 | memcpy(prz->old_log, &buffer->data[start], size - start); | |
272 | memcpy(prz->old_log + size - start, &buffer->data[0], start); | |
c672528a CC |
273 | } |
274 | ||
a15d0b36 | 275 | int notrace persistent_ram_write(struct persistent_ram_zone *prz, |
c672528a CC |
276 | const void *s, unsigned int count) |
277 | { | |
278 | int rem; | |
279 | int c = count; | |
808d0387 | 280 | size_t start; |
c672528a | 281 | |
808d0387 | 282 | if (unlikely(c > prz->buffer_size)) { |
c672528a CC |
283 | s += c - prz->buffer_size; |
284 | c = prz->buffer_size; | |
285 | } | |
808d0387 | 286 | |
484dd30e | 287 | buffer_size_add(prz, c); |
808d0387 CC |
288 | |
289 | start = buffer_start_add(prz, c); | |
290 | ||
291 | rem = prz->buffer_size - start; | |
292 | if (unlikely(rem < c)) { | |
293 | persistent_ram_update(prz, s, start, rem); | |
c672528a CC |
294 | s += rem; |
295 | c -= rem; | |
808d0387 | 296 | start = 0; |
c672528a | 297 | } |
808d0387 | 298 | persistent_ram_update(prz, s, start, c); |
c672528a | 299 | |
9cc05ad9 | 300 | persistent_ram_update_header_ecc(prz); |
c672528a CC |
301 | |
302 | return count; | |
303 | } | |
304 | ||
c672528a CC |
305 | size_t persistent_ram_old_size(struct persistent_ram_zone *prz) |
306 | { | |
307 | return prz->old_log_size; | |
308 | } | |
309 | ||
310 | void *persistent_ram_old(struct persistent_ram_zone *prz) | |
311 | { | |
312 | return prz->old_log; | |
313 | } | |
314 | ||
315 | void persistent_ram_free_old(struct persistent_ram_zone *prz) | |
316 | { | |
317 | kfree(prz->old_log); | |
318 | prz->old_log = NULL; | |
319 | prz->old_log_size = 0; | |
320 | } | |
321 | ||
2b1321e4 | 322 | static void *persistent_ram_vmap(phys_addr_t start, size_t size) |
c672528a | 323 | { |
404a6043 CC |
324 | struct page **pages; |
325 | phys_addr_t page_start; | |
326 | unsigned int page_count; | |
327 | pgprot_t prot; | |
328 | unsigned int i; | |
2b1321e4 | 329 | void *vaddr; |
404a6043 CC |
330 | |
331 | page_start = start - offset_in_page(start); | |
332 | page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); | |
333 | ||
334 | prot = pgprot_noncached(PAGE_KERNEL); | |
335 | ||
336 | pages = kmalloc(sizeof(struct page *) * page_count, GFP_KERNEL); | |
337 | if (!pages) { | |
338 | pr_err("%s: Failed to allocate array for %u pages\n", __func__, | |
339 | page_count); | |
2b1321e4 | 340 | return NULL; |
404a6043 CC |
341 | } |
342 | ||
343 | for (i = 0; i < page_count; i++) { | |
344 | phys_addr_t addr = page_start + i * PAGE_SIZE; | |
345 | pages[i] = pfn_to_page(addr >> PAGE_SHIFT); | |
346 | } | |
2b1321e4 | 347 | vaddr = vmap(pages, page_count, VM_MAP, prot); |
404a6043 | 348 | kfree(pages); |
2b1321e4 AV |
349 | |
350 | return vaddr; | |
351 | } | |
352 | ||
24c3d2f3 AV |
353 | static void *persistent_ram_iomap(phys_addr_t start, size_t size) |
354 | { | |
355 | if (!request_mem_region(start, size, "persistent_ram")) { | |
356 | pr_err("request mem region (0x%llx@0x%llx) failed\n", | |
357 | (unsigned long long)size, (unsigned long long)start); | |
358 | return NULL; | |
359 | } | |
360 | ||
361 | return ioremap(start, size); | |
362 | } | |
363 | ||
2b1321e4 AV |
364 | static int persistent_ram_buffer_map(phys_addr_t start, phys_addr_t size, |
365 | struct persistent_ram_zone *prz) | |
366 | { | |
d3b48769 AV |
367 | prz->paddr = start; |
368 | prz->size = size; | |
369 | ||
24c3d2f3 AV |
370 | if (pfn_valid(start >> PAGE_SHIFT)) |
371 | prz->vaddr = persistent_ram_vmap(start, size); | |
372 | else | |
373 | prz->vaddr = persistent_ram_iomap(start, size); | |
374 | ||
404a6043 | 375 | if (!prz->vaddr) { |
2b1321e4 AV |
376 | pr_err("%s: Failed to map 0x%llx pages at 0x%llx\n", __func__, |
377 | (unsigned long long)size, (unsigned long long)start); | |
404a6043 CC |
378 | return -ENOMEM; |
379 | } | |
380 | ||
381 | prz->buffer = prz->vaddr + offset_in_page(start); | |
382 | prz->buffer_size = size - sizeof(struct persistent_ram_buffer); | |
383 | ||
384 | return 0; | |
385 | } | |
386 | ||
bb4206f2 | 387 | static int __init persistent_ram_post_init(struct persistent_ram_zone *prz, bool ecc) |
404a6043 | 388 | { |
bb4206f2 | 389 | int ret; |
c672528a | 390 | |
9cc05ad9 | 391 | prz->ecc = ecc; |
bb4206f2 | 392 | |
404a6043 | 393 | ret = persistent_ram_init_ecc(prz, prz->buffer_size); |
9cc05ad9 | 394 | if (ret) |
bb4206f2 | 395 | return ret; |
c672528a | 396 | |
404a6043 | 397 | if (prz->buffer->sig == PERSISTENT_RAM_SIG) { |
808d0387 CC |
398 | if (buffer_size(prz) > prz->buffer_size || |
399 | buffer_start(prz) > buffer_size(prz)) | |
400 | pr_info("persistent_ram: found existing invalid buffer," | |
f56d711b | 401 | " size %zu, start %zu\n", |
808d0387 | 402 | buffer_size(prz), buffer_start(prz)); |
c672528a | 403 | else { |
808d0387 | 404 | pr_info("persistent_ram: found existing buffer," |
f56d711b | 405 | " size %zu, start %zu\n", |
808d0387 | 406 | buffer_size(prz), buffer_start(prz)); |
c672528a CC |
407 | persistent_ram_save_old(prz); |
408 | } | |
409 | } else { | |
808d0387 CC |
410 | pr_info("persistent_ram: no valid data in buffer" |
411 | " (sig = 0x%08x)\n", prz->buffer->sig); | |
c672528a CC |
412 | } |
413 | ||
404a6043 | 414 | prz->buffer->sig = PERSISTENT_RAM_SIG; |
808d0387 CC |
415 | atomic_set(&prz->buffer->start, 0); |
416 | atomic_set(&prz->buffer->size, 0); | |
c672528a | 417 | |
bb4206f2 AV |
418 | return 0; |
419 | } | |
420 | ||
d3b48769 AV |
421 | void persistent_ram_free(struct persistent_ram_zone *prz) |
422 | { | |
423 | if (pfn_valid(prz->paddr >> PAGE_SHIFT)) { | |
424 | vunmap(prz->vaddr); | |
425 | } else { | |
426 | iounmap(prz->vaddr); | |
427 | release_mem_region(prz->paddr, prz->size); | |
428 | } | |
429 | persistent_ram_free_old(prz); | |
430 | kfree(prz); | |
431 | } | |
432 | ||
8cf5aff8 AV |
433 | struct persistent_ram_zone * __init persistent_ram_new(phys_addr_t start, |
434 | size_t size, | |
435 | bool ecc) | |
436 | { | |
437 | struct persistent_ram_zone *prz; | |
438 | int ret = -ENOMEM; | |
439 | ||
440 | prz = kzalloc(sizeof(struct persistent_ram_zone), GFP_KERNEL); | |
441 | if (!prz) { | |
442 | pr_err("persistent_ram: failed to allocate persistent ram zone\n"); | |
443 | goto err; | |
444 | } | |
445 | ||
446 | ret = persistent_ram_buffer_map(start, size, prz); | |
447 | if (ret) | |
448 | goto err; | |
449 | ||
450 | persistent_ram_post_init(prz, ecc); | |
451 | persistent_ram_update_header_ecc(prz); | |
452 | ||
453 | return prz; | |
454 | err: | |
455 | kfree(prz); | |
456 | return ERR_PTR(ret); | |
457 | } | |
458 | ||
7dd8e9be AV |
459 | #ifndef MODULE |
460 | static int __init persistent_ram_buffer_init(const char *name, | |
461 | struct persistent_ram_zone *prz) | |
462 | { | |
463 | int i; | |
464 | struct persistent_ram *ram; | |
465 | struct persistent_ram_descriptor *desc; | |
466 | phys_addr_t start; | |
467 | ||
468 | list_for_each_entry(ram, &persistent_ram_list, node) { | |
469 | start = ram->start; | |
470 | for (i = 0; i < ram->num_descs; i++) { | |
471 | desc = &ram->descs[i]; | |
472 | if (!strcmp(desc->name, name)) | |
473 | return persistent_ram_buffer_map(start, | |
474 | desc->size, prz); | |
475 | start += desc->size; | |
476 | } | |
477 | } | |
478 | ||
479 | return -EINVAL; | |
480 | } | |
481 | ||
bb4206f2 AV |
482 | static __init |
483 | struct persistent_ram_zone *__persistent_ram_init(struct device *dev, bool ecc) | |
484 | { | |
485 | struct persistent_ram_zone *prz; | |
486 | int ret = -ENOMEM; | |
487 | ||
488 | prz = kzalloc(sizeof(struct persistent_ram_zone), GFP_KERNEL); | |
489 | if (!prz) { | |
490 | pr_err("persistent_ram: failed to allocate persistent ram zone\n"); | |
491 | goto err; | |
492 | } | |
493 | ||
494 | ret = persistent_ram_buffer_init(dev_name(dev), prz); | |
495 | if (ret) { | |
496 | pr_err("persistent_ram: failed to initialize buffer\n"); | |
497 | goto err; | |
498 | } | |
499 | ||
500 | persistent_ram_post_init(prz, ecc); | |
501 | ||
404a6043 | 502 | return prz; |
474a8988 JJ |
503 | err: |
504 | kfree(prz); | |
505 | return ERR_PTR(ret); | |
404a6043 | 506 | } |
c672528a | 507 | |
404a6043 CC |
508 | struct persistent_ram_zone * __init |
509 | persistent_ram_init_ringbuffer(struct device *dev, bool ecc) | |
510 | { | |
511 | return __persistent_ram_init(dev, ecc); | |
c672528a CC |
512 | } |
513 | ||
404a6043 | 514 | int __init persistent_ram_early_init(struct persistent_ram *ram) |
c672528a | 515 | { |
404a6043 CC |
516 | int ret; |
517 | ||
518 | ret = memblock_reserve(ram->start, ram->size); | |
519 | if (ret) { | |
520 | pr_err("Failed to reserve persistent memory from %08lx-%08lx\n", | |
521 | (long)ram->start, (long)(ram->start + ram->size - 1)); | |
522 | return ret; | |
523 | } | |
524 | ||
525 | list_add_tail(&ram->node, &persistent_ram_list); | |
526 | ||
527 | pr_info("Initialized persistent memory from %08lx-%08lx\n", | |
528 | (long)ram->start, (long)(ram->start + ram->size - 1)); | |
529 | ||
530 | return 0; | |
c672528a | 531 | } |
7dd8e9be | 532 | #endif |