1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // originally in linux/arch/arm/plat-s3c24xx/pm.c |
4 | // |
5 | // Copyright (c) 2004-2008 Simtec Electronics |
6 | // http://armlinux.simtec.co.uk |
7 | // Ben Dooks <ben@simtec.co.uk> |
8 | // |
9 | // S3C Power Mangament - suspend/resume memory corruption check. |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/suspend.h> |
13 | #include <linux/init.h> |
14 | #include <linux/crc32.h> |
15 | #include <linux/ioport.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #include <linux/soc/samsung/s3c-pm.h> |
19 | |
20 | #if CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE < 1 |
21 | #error CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE must be a positive non-zero value |
22 | #endif |
23 | |
24 | /* suspend checking code... |
25 | * |
26 | * this next area does a set of crc checks over all the installed |
27 | * memory, so the system can verify if the resume was ok. |
28 | * |
29 | * CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE defines the block-size for the CRC, |
30 | * increasing it will mean that the area corrupted will be less easy to spot, |
31 | * and reducing the size will cause the CRC save area to grow |
32 | */ |
33 | |
34 | #define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024) |
35 | |
36 | static u32 crc_size; /* size needed for the crc block */ |
37 | static u32 *crcs; /* allocated over suspend/resume */ |
38 | |
39 | typedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg); |
40 | |
41 | /* s3c_pm_run_res |
42 | * |
43 | * go through the given resource list, and look for system ram |
44 | */ |
45 | |
46 | static void s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg) |
47 | { |
48 | while (ptr != NULL) { |
49 | if (ptr->child != NULL) |
50 | s3c_pm_run_res(ptr: ptr->child, fn, arg); |
51 | |
52 | if ((ptr->flags & IORESOURCE_SYSTEM_RAM) |
53 | == IORESOURCE_SYSTEM_RAM) { |
54 | S3C_PMDBG("Found system RAM at %08lx..%08lx\n" , |
55 | (unsigned long)ptr->start, |
56 | (unsigned long)ptr->end); |
57 | arg = (fn)(ptr, arg); |
58 | } |
59 | |
60 | ptr = ptr->sibling; |
61 | } |
62 | } |
63 | |
64 | static void s3c_pm_run_sysram(run_fn_t fn, u32 *arg) |
65 | { |
66 | s3c_pm_run_res(ptr: &iomem_resource, fn, arg); |
67 | } |
68 | |
69 | static u32 *s3c_pm_countram(struct resource *res, u32 *val) |
70 | { |
71 | u32 size = (u32)resource_size(res); |
72 | |
73 | size += CHECK_CHUNKSIZE-1; |
74 | size /= CHECK_CHUNKSIZE; |
75 | |
76 | S3C_PMDBG("Area %08lx..%08lx, %d blocks\n" , |
77 | (unsigned long)res->start, (unsigned long)res->end, size); |
78 | |
79 | *val += size * sizeof(u32); |
80 | return val; |
81 | } |
82 | |
83 | /* s3c_pm_prepare_check |
84 | * |
85 | * prepare the necessary information for creating the CRCs. This |
86 | * must be done before the final save, as it will require memory |
87 | * allocating, and thus touching bits of the kernel we do not |
88 | * know about. |
89 | */ |
90 | |
91 | void s3c_pm_check_prepare(void) |
92 | { |
93 | crc_size = 0; |
94 | |
95 | s3c_pm_run_sysram(s3c_pm_countram, &crc_size); |
96 | |
97 | S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n" , crc_size); |
98 | |
99 | crcs = kmalloc(crc_size+4, GFP_KERNEL); |
100 | if (crcs == NULL) |
101 | printk(KERN_ERR "Cannot allocated CRC save area\n" ); |
102 | } |
103 | |
104 | static u32 *s3c_pm_makecheck(struct resource *res, u32 *val) |
105 | { |
106 | unsigned long addr, left; |
107 | |
108 | for (addr = res->start; addr < res->end; |
109 | addr += CHECK_CHUNKSIZE) { |
110 | left = res->end - addr; |
111 | |
112 | if (left > CHECK_CHUNKSIZE) |
113 | left = CHECK_CHUNKSIZE; |
114 | |
115 | *val = crc32_le(crc: ~0, p: phys_to_virt(addr), len: left); |
116 | val++; |
117 | } |
118 | |
119 | return val; |
120 | } |
121 | |
122 | /* s3c_pm_check_store |
123 | * |
124 | * compute the CRC values for the memory blocks before the final |
125 | * sleep. |
126 | */ |
127 | |
128 | void s3c_pm_check_store(void) |
129 | { |
130 | if (crcs != NULL) |
131 | s3c_pm_run_sysram(s3c_pm_makecheck, crcs); |
132 | } |
133 | |
134 | /* in_region |
135 | * |
136 | * return TRUE if the area defined by ptr..ptr+size contains the |
137 | * what..what+whatsz |
138 | */ |
139 | |
140 | static inline int in_region(void *ptr, int size, void *what, size_t whatsz) |
141 | { |
142 | if ((what+whatsz) < ptr) |
143 | return 0; |
144 | |
145 | if (what > (ptr+size)) |
146 | return 0; |
147 | |
148 | return 1; |
149 | } |
150 | |
151 | /** |
152 | * s3c_pm_runcheck() - helper to check a resource on restore. |
153 | * @res: The resource to check |
154 | * @val: Pointer to list of CRC32 values to check. |
155 | * |
156 | * Called from the s3c_pm_check_restore() via s3c_pm_run_sysram(), this |
157 | * function runs the given memory resource checking it against the stored |
158 | * CRC to ensure that memory is restored. The function tries to skip as |
159 | * many of the areas used during the suspend process. |
160 | */ |
161 | static u32 *s3c_pm_runcheck(struct resource *res, u32 *val) |
162 | { |
163 | unsigned long addr; |
164 | unsigned long left; |
165 | void *stkpage; |
166 | void *ptr; |
167 | u32 calc; |
168 | |
169 | stkpage = (void *)((u32)&calc & ~PAGE_MASK); |
170 | |
171 | for (addr = res->start; addr < res->end; |
172 | addr += CHECK_CHUNKSIZE) { |
173 | left = res->end - addr; |
174 | |
175 | if (left > CHECK_CHUNKSIZE) |
176 | left = CHECK_CHUNKSIZE; |
177 | |
178 | ptr = phys_to_virt(addr); |
179 | |
180 | if (in_region(ptr, size: left, what: stkpage, whatsz: 4096)) { |
181 | S3C_PMDBG("skipping %08lx, has stack in\n" , addr); |
182 | goto skip_check; |
183 | } |
184 | |
185 | if (in_region(ptr, size: left, what: crcs, whatsz: crc_size)) { |
186 | S3C_PMDBG("skipping %08lx, has crc block in\n" , addr); |
187 | goto skip_check; |
188 | } |
189 | |
190 | /* calculate and check the checksum */ |
191 | |
192 | calc = crc32_le(crc: ~0, p: ptr, len: left); |
193 | if (calc != *val) { |
194 | printk(KERN_ERR "Restore CRC error at " |
195 | "%08lx (%08x vs %08x)\n" , addr, calc, *val); |
196 | |
197 | S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n" , |
198 | addr, calc, *val); |
199 | } |
200 | |
201 | skip_check: |
202 | val++; |
203 | } |
204 | |
205 | return val; |
206 | } |
207 | |
208 | /** |
209 | * s3c_pm_check_restore() - memory check called on resume |
210 | * |
211 | * check the CRCs after the restore event and free the memory used |
212 | * to hold them |
213 | */ |
214 | void s3c_pm_check_restore(void) |
215 | { |
216 | if (crcs != NULL) |
217 | s3c_pm_run_sysram(s3c_pm_runcheck, crcs); |
218 | } |
219 | |
220 | /** |
221 | * s3c_pm_check_cleanup() - free memory resources |
222 | * |
223 | * Free the resources that where allocated by the suspend |
224 | * memory check code. We do this separately from the |
225 | * s3c_pm_check_restore() function as we cannot call any |
226 | * functions that might sleep during that resume. |
227 | */ |
228 | void s3c_pm_check_cleanup(void) |
229 | { |
230 | kfree(crcs); |
231 | crcs = NULL; |
232 | } |
233 | |
234 | |