Files
2018-03-13 20:29:02 +01:00

379 lines
9.9 KiB
C

/*
* kernel/power/incremental.c
*
* Copyright (C) 2012 Nigel Cunningham (nigel at tuxonice net)
*
* This file is released under the GPLv2.
*
* This file contains routines related to storing incremental images - that
* is, retaining an image after an initial cycle and then storing incremental
* changes on subsequent hibernations.
*/
#include <linux/suspend.h>
#include <linux/highmem.h>
#include <linux/vmalloc.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include "tuxonice_builtin.h"
#include "tuxonice.h"
#include "tuxonice_modules.h"
#include "tuxonice_sysfs.h"
#include "tuxonice_io.h"
#include "tuxonice_ui.h"
#include "tuxonice_alloc.h"
static struct toi_module_ops toi_incremental_ops;
static struct toi_module_ops *next_driver;
static unsigned long toi_incremental_bytes_in, toi_incremental_bytes_out;
static char toi_incremental_slow_cmp_name[32] = "sha1";
static int toi_incremental_digestsize;
static DEFINE_MUTEX(stats_lock);
struct toi_cpu_context {
u8 *buffer_start;
struct hash_desc desc;
struct scatterlist sg[1];
unsigned char *digest;
};
#define OUT_BUF_SIZE (2 * PAGE_SIZE)
static DEFINE_PER_CPU(struct toi_cpu_context, contexts);
/*
* toi_crypto_prepare
*
* Prepare to do some work by allocating buffers and transforms.
*/
static int toi_incremental_crypto_prepare(void)
{
int cpu, digestsize = toi_incremental_digestsize;
if (!*toi_incremental_slow_cmp_name) {
printk(KERN_INFO "TuxOnIce: Incremental image support enabled but no "
"hash algorithm set.\n");
return 1;
}
for_each_online_cpu(cpu) {
struct toi_cpu_context *this = &per_cpu(contexts, cpu);
this->desc.tfm = crypto_alloc_hash(toi_incremental_slow_cmp_name, 0, 0);
if (IS_ERR(this->desc.tfm)) {
printk(KERN_INFO "TuxOnIce: Failed to initialise the "
"%s hashing transform.\n", toi_incremental_slow_cmp_name);
this->desc.tfm = NULL;
return 1;
}
if (!digestsize) {
digestsize = crypto_hash_digestsize(this->desc.tfm);
toi_incremental_digestsize = digestsize;
}
this->digest = toi_kzalloc(16, digestsize, GFP_KERNEL);
if (!this->digest)
return -ENOMEM;
this->desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
}
return 0;
}
static int toi_incremental_rw_cleanup(int writing)
{
int cpu;
for_each_online_cpu(cpu) {
struct toi_cpu_context *this = &per_cpu(contexts, cpu);
if (this->desc.tfm) {
crypto_free_hash(this->desc.tfm);
this->desc.tfm = NULL;
}
if (this->digest) {
toi_kfree(16, this->digest, toi_incremental_digestsize);
this->digest = NULL;
}
}
return 0;
}
/*
* toi_incremental_init
*/
static int toi_incremental_init(int hibernate_or_resume)
{
if (!hibernate_or_resume)
return 0;
next_driver = toi_get_next_filter(&toi_incremental_ops);
return next_driver ? 0 : -ECHILD;
}
/*
* toi_incremental_rw_init()
*/
static int toi_incremental_rw_init(int rw, int stream_number)
{
if (rw == WRITE && toi_incremental_crypto_prepare()) {
printk(KERN_ERR "Failed to initialise hashing " "algorithm.\n");
if (rw == READ) {
printk(KERN_INFO "Unable to read the image.\n");
return -ENODEV;
} else {
printk(KERN_INFO "Continuing without "
" calculating an incremental image.\n");
toi_incremental_ops.enabled = 0;
}
}
return 0;
}
/*
* toi_incremental_write_page()
*
* Decide whether to write a page to the image. Calculate the SHA1 (or something
* else if the user changes the hashing algo) of the page and compare it to the
* previous value (if any). If there was no previous value or the values are
* different, write the page. Otherwise, skip the write.
*
* @TODO: Clear hashes for pages that are no longer in the image!
*
* Buffer_page: Pointer to a buffer of size PAGE_SIZE, containing
* data to be written.
*
* Returns: 0 on success. Otherwise the error is that returned by later
* modules, -ECHILD if we have a broken pipeline or -EIO if
* zlib errs.
*/
static int toi_incremental_write_page(unsigned long index, int buf_type,
void *buffer_page, unsigned int buf_size)
{
int ret = 0, cpu = smp_processor_id();
struct toi_cpu_context *ctx = &per_cpu(contexts, cpu);
int to_write = true;
if (ctx->desc.tfm) {
/* char *old_hash; */
ctx->buffer_start = TOI_MAP(buf_type, buffer_page);
sg_init_one(&ctx->sg[0], ctx->buffer_start, buf_size);
ret = crypto_hash_digest(&ctx->desc, &ctx->sg[0], ctx->sg[0].length, ctx->digest);
/* old_hash = get_old_hash(index); */
TOI_UNMAP(buf_type, buffer_page);
#if 0
if (!ret && new_hash == old_hash) {
to_write = false;
} else
store_hash(ctx, index, new_hash);
#endif
}
mutex_lock(&stats_lock);
toi_incremental_bytes_in += buf_size;
if (ret || to_write)
toi_incremental_bytes_out += buf_size;
mutex_unlock(&stats_lock);
if (ret || to_write) {
int ret2 = next_driver->write_page(index, buf_type,
buffer_page, buf_size);
if (!ret)
ret = ret2;
}
return ret;
}
/*
* toi_incremental_read_page()
* @buffer_page: struct page *. Pointer to a buffer of size PAGE_SIZE.
*
* Nothing extra to do here.
*/
static int toi_incremental_read_page(unsigned long *index, int buf_type,
void *buffer_page, unsigned int *buf_size)
{
return next_driver->read_page(index, TOI_PAGE, buffer_page, buf_size);
}
/*
* toi_incremental_print_debug_stats
* @buffer: Pointer to a buffer into which the debug info will be printed.
* @size: Size of the buffer.
*
* Print information to be recorded for debugging purposes into a buffer.
* Returns: Number of characters written to the buffer.
*/
static int toi_incremental_print_debug_stats(char *buffer, int size)
{
unsigned long pages_in = toi_incremental_bytes_in >> PAGE_SHIFT,
pages_out = toi_incremental_bytes_out >> PAGE_SHIFT;
int len;
/* Output the size of the incremental image. */
if (*toi_incremental_slow_cmp_name)
len = scnprintf(buffer, size, "- Hash algorithm is '%s'.\n",
toi_incremental_slow_cmp_name);
else
len = scnprintf(buffer, size, "- Hash algorithm is not set.\n");
if (pages_in)
len += scnprintf(buffer + len, size - len, " Incremental image "
"%lu of %lu bytes (%ld percent).\n",
toi_incremental_bytes_out,
toi_incremental_bytes_in, pages_out * 100 / pages_in);
return len;
}
/*
* toi_incremental_memory_needed
*
* Tell the caller how much memory we need to operate during hibernate/resume.
* Returns: Unsigned long. Maximum number of bytes of memory required for
* operation.
*/
static int toi_incremental_memory_needed(void)
{
return 2 * PAGE_SIZE;
}
static int toi_incremental_storage_needed(void)
{
return 2 * sizeof(unsigned long) + sizeof(int) + strlen(toi_incremental_slow_cmp_name) + 1;
}
/*
* toi_incremental_save_config_info
* @buffer: Pointer to a buffer of size PAGE_SIZE.
*
* Save informaton needed when reloading the image at resume time.
* Returns: Number of bytes used for saving our data.
*/
static int toi_incremental_save_config_info(char *buffer)
{
int len = strlen(toi_incremental_slow_cmp_name) + 1, offset = 0;
*((unsigned long *)buffer) = toi_incremental_bytes_in;
offset += sizeof(unsigned long);
*((unsigned long *)(buffer + offset)) = toi_incremental_bytes_out;
offset += sizeof(unsigned long);
*((int *)(buffer + offset)) = len;
offset += sizeof(int);
strncpy(buffer + offset, toi_incremental_slow_cmp_name, len);
return offset + len;
}
/* toi_incremental_load_config_info
* @buffer: Pointer to the start of the data.
* @size: Number of bytes that were saved.
*
* Description: Reload information to be retained for debugging info.
*/
static void toi_incremental_load_config_info(char *buffer, int size)
{
int len, offset = 0;
toi_incremental_bytes_in = *((unsigned long *)buffer);
offset += sizeof(unsigned long);
toi_incremental_bytes_out = *((unsigned long *)(buffer + offset));
offset += sizeof(unsigned long);
len = *((int *)(buffer + offset));
offset += sizeof(int);
strncpy(toi_incremental_slow_cmp_name, buffer + offset, len);
}
static void toi_incremental_pre_atomic_restore(struct toi_boot_kernel_data *bkd)
{
bkd->incremental_bytes_in = toi_incremental_bytes_in;
bkd->incremental_bytes_out = toi_incremental_bytes_out;
}
static void toi_incremental_post_atomic_restore(struct toi_boot_kernel_data *bkd)
{
toi_incremental_bytes_in = bkd->incremental_bytes_in;
toi_incremental_bytes_out = bkd->incremental_bytes_out;
}
static void toi_incremental_algo_change(void)
{
/* Reset so it's gotten from crypto_hash_digestsize afresh */
toi_incremental_digestsize = 0;
}
/*
* data for our sysfs entries.
*/
static struct toi_sysfs_data sysfs_params[] = {
SYSFS_INT("enabled", SYSFS_RW, &toi_incremental_ops.enabled, 0, 1, 0,
NULL),
SYSFS_STRING("algorithm", SYSFS_RW, toi_incremental_slow_cmp_name, 31, 0,
toi_incremental_algo_change),
};
/*
* Ops structure.
*/
static struct toi_module_ops toi_incremental_ops = {
.type = FILTER_MODULE,
.name = "incremental",
.directory = "incremental",
.module = THIS_MODULE,
.initialise = toi_incremental_init,
.memory_needed = toi_incremental_memory_needed,
.print_debug_info = toi_incremental_print_debug_stats,
.save_config_info = toi_incremental_save_config_info,
.load_config_info = toi_incremental_load_config_info,
.storage_needed = toi_incremental_storage_needed,
.pre_atomic_restore = toi_incremental_pre_atomic_restore,
.post_atomic_restore = toi_incremental_post_atomic_restore,
.rw_init = toi_incremental_rw_init,
.rw_cleanup = toi_incremental_rw_cleanup,
.write_page = toi_incremental_write_page,
.read_page = toi_incremental_read_page,
.sysfs_data = sysfs_params,
.num_sysfs_entries = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data),
};
/* ---- Registration ---- */
static __init int toi_incremental_load(void)
{
return toi_register_module(&toi_incremental_ops);
}
#ifdef MODULE
static __exit void toi_incremental_unload(void)
{
toi_unregister_module(&toi_incremental_ops);
}
module_init(toi_incremental_load);
module_exit(toi_incremental_unload);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nigel Cunningham");
MODULE_DESCRIPTION("Incremental Image Support for TuxOnIce");
#else
late_initcall(toi_incremental_load);
#endif