Commit | Line | Data |
---|---|---|
6fa3eb70 S |
1 | /* |
2 | * kernel/power/tuxonice_prune.c | |
3 | * | |
4 | * Copyright (C) 2012 Nigel Cunningham (nigel at tuxonice net) | |
5 | * | |
6 | * This file is released under the GPLv2. | |
7 | * | |
8 | * This file implements a TuxOnIce module that seeks to prune the | |
9 | * amount of data written to disk. It builds a table of hashes | |
10 | * of the uncompressed data, and writes the pfn of the previous page | |
11 | * with the same contents instead of repeating the data when a match | |
12 | * is found. | |
13 | */ | |
14 | ||
15 | #include <linux/suspend.h> | |
16 | #include <linux/highmem.h> | |
17 | #include <linux/vmalloc.h> | |
18 | #include <linux/crypto.h> | |
19 | #include <linux/scatterlist.h> | |
20 | #include <crypto/hash.h> | |
21 | ||
22 | #include "tuxonice_builtin.h" | |
23 | #include "tuxonice.h" | |
24 | #include "tuxonice_modules.h" | |
25 | #include "tuxonice_sysfs.h" | |
26 | #include "tuxonice_io.h" | |
27 | #include "tuxonice_ui.h" | |
28 | #include "tuxonice_alloc.h" | |
29 | ||
30 | /* | |
31 | * We never write a page bigger than PAGE_SIZE, so use a large number | |
32 | * to indicate that data is a PFN. | |
33 | */ | |
34 | #define PRUNE_DATA_IS_PFN (PAGE_SIZE + 100) | |
35 | ||
36 | static unsigned long toi_pruned_pages; | |
37 | ||
38 | static struct toi_module_ops toi_prune_ops; | |
39 | static struct toi_module_ops *next_driver; | |
40 | ||
41 | static char toi_prune_hash_algo_name[32] = "sha1"; | |
42 | ||
43 | static DEFINE_MUTEX(stats_lock); | |
44 | ||
45 | struct toi_cpu_context { | |
46 | struct shash_desc desc; | |
47 | char *digest; | |
48 | }; | |
49 | ||
50 | #define OUT_BUF_SIZE (2 * PAGE_SIZE) | |
51 | ||
52 | static DEFINE_PER_CPU(struct toi_cpu_context, contexts); | |
53 | ||
54 | /* | |
55 | * toi_crypto_prepare | |
56 | * | |
57 | * Prepare to do some work by allocating buffers and transforms. | |
58 | */ | |
59 | static int toi_prune_crypto_prepare(void) | |
60 | { | |
61 | int cpu, ret, digestsize; | |
62 | ||
63 | if (!*toi_prune_hash_algo_name) { | |
64 | printk(KERN_INFO "TuxOnIce: Pruning enabled but no " "hash algorithm set.\n"); | |
65 | return 1; | |
66 | } | |
67 | ||
68 | for_each_online_cpu(cpu) { | |
69 | struct toi_cpu_context *this = &per_cpu(contexts, cpu); | |
70 | this->desc.tfm = crypto_alloc_shash(toi_prune_hash_algo_name, 0, 0); | |
71 | if (IS_ERR(this->desc.tfm)) { | |
72 | printk(KERN_INFO "TuxOnIce: Failed to allocate the " | |
73 | "%s prune hash algorithm.\n", toi_prune_hash_algo_name); | |
74 | this->desc.tfm = NULL; | |
75 | return 1; | |
76 | } | |
77 | ||
78 | if (!digestsize) | |
79 | digestsize = crypto_shash_digestsize(this->desc.tfm); | |
80 | ||
81 | this->digest = kmalloc(digestsize, GFP_KERNEL); | |
82 | if (!this->digest) { | |
83 | printk(KERN_INFO "TuxOnIce: Failed to allocate space " | |
84 | "for digest output.\n"); | |
85 | crypto_free_shash(this->desc.tfm); | |
86 | this->desc.tfm = NULL; | |
87 | } | |
88 | ||
89 | this->desc.flags = 0; | |
90 | ||
91 | ret = crypto_shash_init(&this->desc); | |
92 | if (ret < 0) { | |
93 | printk(KERN_INFO "TuxOnIce: Failed to initialise the " | |
94 | "%s prune hash algorithm.\n", toi_prune_hash_algo_name); | |
95 | kfree(this->digest); | |
96 | this->digest = NULL; | |
97 | crypto_free_shash(this->desc.tfm); | |
98 | this->desc.tfm = NULL; | |
99 | return 1; | |
100 | } | |
101 | } | |
102 | ||
103 | return 0; | |
104 | } | |
105 | ||
106 | static int toi_prune_rw_cleanup(int writing) | |
107 | { | |
108 | int cpu; | |
109 | ||
110 | for_each_online_cpu(cpu) { | |
111 | struct toi_cpu_context *this = &per_cpu(contexts, cpu); | |
112 | if (this->desc.tfm) { | |
113 | crypto_free_shash(this->desc.tfm); | |
114 | this->desc.tfm = NULL; | |
115 | } | |
116 | ||
117 | if (this->digest) { | |
118 | kfree(this->digest); | |
119 | this->digest = NULL; | |
120 | } | |
121 | } | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | /* | |
127 | * toi_prune_init | |
128 | */ | |
129 | ||
130 | static int toi_prune_init(int toi_or_resume) | |
131 | { | |
132 | if (!toi_or_resume) | |
133 | return 0; | |
134 | ||
135 | toi_pruned_pages = 0; | |
136 | ||
137 | next_driver = toi_get_next_filter(&toi_prune_ops); | |
138 | ||
139 | return next_driver ? 0 : -ECHILD; | |
140 | } | |
141 | ||
142 | /* | |
143 | * toi_prune_rw_init() | |
144 | */ | |
145 | ||
146 | static int toi_prune_rw_init(int rw, int stream_number) | |
147 | { | |
148 | if (toi_prune_crypto_prepare()) { | |
149 | printk(KERN_ERR "Failed to initialise prune " "algorithm.\n"); | |
150 | if (rw == READ) { | |
151 | printk(KERN_INFO "Unable to read the image.\n"); | |
152 | return -ENODEV; | |
153 | } else { | |
154 | printk(KERN_INFO "Continuing without " "pruning the image.\n"); | |
155 | toi_prune_ops.enabled = 0; | |
156 | } | |
157 | } | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | /* | |
163 | * toi_prune_write_page() | |
164 | * | |
165 | * Compress a page of data, buffering output and passing on filled | |
166 | * pages to the next module in the pipeline. | |
167 | * | |
168 | * Buffer_page: Pointer to a buffer of size PAGE_SIZE, containing | |
169 | * data to be checked. | |
170 | * | |
171 | * Returns: 0 on success. Otherwise the error is that returned by later | |
172 | * modules, -ECHILD if we have a broken pipeline or -EIO if | |
173 | * zlib errs. | |
174 | */ | |
175 | static int toi_prune_write_page(unsigned long index, int buf_type, | |
176 | void *buffer_page, unsigned int buf_size) | |
177 | { | |
178 | int ret = 0, cpu = smp_processor_id(), write_data = 1; | |
179 | struct toi_cpu_context *ctx = &per_cpu(contexts, cpu); | |
180 | u8 *output_buffer = buffer_page; | |
181 | int output_len = buf_size; | |
182 | int out_buf_type = buf_type; | |
183 | void *buffer_start; | |
184 | u32 buf[4]; | |
185 | ||
186 | if (ctx->desc.tfm) { | |
187 | ||
188 | buffer_start = TOI_MAP(buf_type, buffer_page); | |
189 | ctx->len = OUT_BUF_SIZE; | |
190 | ||
191 | ret = crypto_shash_digest(&ctx->desc, buffer_start, buf_size, &ctx->digest); | |
192 | if (ret) { | |
193 | printk(KERN_INFO "TuxOnIce: Failed to calculate digest (%d).\n", ret); | |
194 | } else { | |
195 | mutex_lock(&stats_lock); | |
196 | ||
197 | toi_pruned_pages++; | |
198 | ||
199 | mutex_unlock(&stats_lock); | |
200 | ||
201 | } | |
202 | ||
203 | TOI_UNMAP(buf_type, buffer_page); | |
204 | } | |
205 | ||
206 | if (write_data) | |
207 | ret = next_driver->write_page(index, out_buf_type, output_buffer, output_len); | |
208 | else | |
209 | ret = next_driver->write_page(index, out_buf_type, output_buffer, output_len); | |
210 | ||
211 | return ret; | |
212 | } | |
213 | ||
214 | /* | |
215 | * toi_prune_read_page() | |
216 | * @buffer_page: struct page *. Pointer to a buffer of size PAGE_SIZE. | |
217 | * | |
218 | * Retrieve data from later modules or from a previously loaded page and | |
219 | * fill the input buffer. | |
220 | * Zero if successful. Error condition from me or from downstream on failure. | |
221 | */ | |
222 | static int toi_prune_read_page(unsigned long *index, int buf_type, | |
223 | void *buffer_page, unsigned int *buf_size) | |
224 | { | |
225 | int ret, cpu = smp_processor_id(); | |
226 | unsigned int len; | |
227 | char *buffer_start; | |
228 | struct toi_cpu_context *ctx = &per_cpu(contexts, cpu); | |
229 | ||
230 | if (!ctx->desc.tfm) | |
231 | return next_driver->read_page(index, TOI_PAGE, buffer_page, buf_size); | |
232 | ||
233 | /* | |
234 | * All our reads must be synchronous - we can't handle | |
235 | * data that hasn't been read yet. | |
236 | */ | |
237 | ||
238 | ret = next_driver->read_page(index, buf_type, buffer_page, &len); | |
239 | ||
240 | if (len == PRUNE_DATA_IS_PFN) { | |
241 | buffer_start = kmap(buffer_page); | |
242 | } | |
243 | ||
244 | return ret; | |
245 | } | |
246 | ||
247 | /* | |
248 | * toi_prune_print_debug_stats | |
249 | * @buffer: Pointer to a buffer into which the debug info will be printed. | |
250 | * @size: Size of the buffer. | |
251 | * | |
252 | * Print information to be recorded for debugging purposes into a buffer. | |
253 | * Returns: Number of characters written to the buffer. | |
254 | */ | |
255 | ||
256 | static int toi_prune_print_debug_stats(char *buffer, int size) | |
257 | { | |
258 | int len; | |
259 | ||
260 | /* Output the number of pages pruned. */ | |
261 | if (*toi_prune_hash_algo_name) | |
262 | len = scnprintf(buffer, size, "- Compressor is '%s'.\n", toi_prune_hash_algo_name); | |
263 | else | |
264 | len = scnprintf(buffer, size, "- Compressor is not set.\n"); | |
265 | ||
266 | if (toi_pruned_pages) | |
267 | len += scnprintf(buffer + len, size - len, " Pruned " | |
268 | "%lu pages).\n", toi_pruned_pages); | |
269 | return len; | |
270 | } | |
271 | ||
272 | /* | |
273 | * toi_prune_memory_needed | |
274 | * | |
275 | * Tell the caller how much memory we need to operate during hibernate/resume. | |
276 | * Returns: Unsigned long. Maximum number of bytes of memory required for | |
277 | * operation. | |
278 | */ | |
279 | static int toi_prune_memory_needed(void) | |
280 | { | |
281 | return 2 * PAGE_SIZE; | |
282 | } | |
283 | ||
284 | static int toi_prune_storage_needed(void) | |
285 | { | |
286 | return 2 * sizeof(unsigned long) + 2 * sizeof(int) + strlen(toi_prune_hash_algo_name) + 1; | |
287 | } | |
288 | ||
289 | /* | |
290 | * toi_prune_save_config_info | |
291 | * @buffer: Pointer to a buffer of size PAGE_SIZE. | |
292 | * | |
293 | * Save informaton needed when reloading the image at resume time. | |
294 | * Returns: Number of bytes used for saving our data. | |
295 | */ | |
296 | static int toi_prune_save_config_info(char *buffer) | |
297 | { | |
298 | int len = strlen(toi_prune_hash_algo_name) + 1, offset = 0; | |
299 | ||
300 | *((unsigned long *)buffer) = toi_pruned_pages; | |
301 | offset += sizeof(unsigned long); | |
302 | *((int *)(buffer + offset)) = len; | |
303 | offset += sizeof(int); | |
304 | strncpy(buffer + offset, toi_prune_hash_algo_name, len); | |
305 | return offset + len; | |
306 | } | |
307 | ||
308 | /* toi_prune_load_config_info | |
309 | * @buffer: Pointer to the start of the data. | |
310 | * @size: Number of bytes that were saved. | |
311 | * | |
312 | * Description: Reload information needed for passing back to the | |
313 | * resumed kernel. | |
314 | */ | |
315 | static void toi_prune_load_config_info(char *buffer, int size) | |
316 | { | |
317 | int len, offset = 0; | |
318 | ||
319 | toi_pruned_pages = *((unsigned long *)buffer); | |
320 | offset += sizeof(unsigned long); | |
321 | len = *((int *)(buffer + offset)); | |
322 | offset += sizeof(int); | |
323 | strncpy(toi_prune_hash_algo_name, buffer + offset, len); | |
324 | } | |
325 | ||
326 | static void toi_prune_pre_atomic_restore(struct toi_boot_kernel_data *bkd) | |
327 | { | |
328 | bkd->pruned_pages = toi_pruned_pages; | |
329 | } | |
330 | ||
331 | static void toi_prune_post_atomic_restore(struct toi_boot_kernel_data *bkd) | |
332 | { | |
333 | toi_pruned_pages = bkd->pruned_pages; | |
334 | } | |
335 | ||
336 | /* | |
337 | * toi_expected_ratio | |
338 | * | |
339 | * Description: Returns the expected ratio between data passed into this module | |
340 | * and the amount of data output when writing. | |
341 | * Returns: 100 - we have no idea how many pages will be pruned. | |
342 | */ | |
343 | ||
344 | static int toi_prune_expected_ratio(void) | |
345 | { | |
346 | return 100; | |
347 | } | |
348 | ||
349 | /* | |
350 | * data for our sysfs entries. | |
351 | */ | |
352 | static struct toi_sysfs_data sysfs_params[] = { | |
353 | SYSFS_INT("enabled", SYSFS_RW, &toi_prune_ops.enabled, 0, 1, 0, | |
354 | NULL), | |
355 | SYSFS_STRING("algorithm", SYSFS_RW, toi_prune_hash_algo_name, 31, 0, NULL), | |
356 | }; | |
357 | ||
358 | /* | |
359 | * Ops structure. | |
360 | */ | |
361 | static struct toi_module_ops toi_prune_ops = { | |
362 | .type = FILTER_MODULE, | |
363 | .name = "prune", | |
364 | .directory = "prune", | |
365 | .module = THIS_MODULE, | |
366 | .initialise = toi_prune_init, | |
367 | .memory_needed = toi_prune_memory_needed, | |
368 | .print_debug_info = toi_prune_print_debug_stats, | |
369 | .save_config_info = toi_prune_save_config_info, | |
370 | .load_config_info = toi_prune_load_config_info, | |
371 | .storage_needed = toi_prune_storage_needed, | |
372 | .expected_compression = toi_prune_expected_ratio, | |
373 | ||
374 | .pre_atomic_restore = toi_prune_pre_atomic_restore, | |
375 | .post_atomic_restore = toi_prune_post_atomic_restore, | |
376 | ||
377 | .rw_init = toi_prune_rw_init, | |
378 | .rw_cleanup = toi_prune_rw_cleanup, | |
379 | ||
380 | .write_page = toi_prune_write_page, | |
381 | .read_page = toi_prune_read_page, | |
382 | ||
383 | .sysfs_data = sysfs_params, | |
384 | .num_sysfs_entries = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data), | |
385 | }; | |
386 | ||
387 | /* ---- Registration ---- */ | |
388 | ||
389 | static __init int toi_prune_load(void) | |
390 | { | |
391 | return toi_register_module(&toi_prune_ops); | |
392 | } | |
393 | ||
394 | #ifdef MODULE | |
395 | static __exit void toi_prune_unload(void) | |
396 | { | |
397 | toi_unregister_module(&toi_prune_ops); | |
398 | } | |
399 | module_init(toi_prune_load); | |
400 | module_exit(toi_prune_unload); | |
401 | MODULE_LICENSE("GPL"); | |
402 | MODULE_AUTHOR("Nigel Cunningham"); | |
403 | MODULE_DESCRIPTION("Image Pruning Support for TuxOnIce"); | |
404 | #else | |
405 | late_initcall(toi_prune_load); | |
406 | #endif |