531 lines
12 KiB
C
531 lines
12 KiB
C
/*
|
|
* kernel/power/tuxonice_modules.c
|
|
*
|
|
* Copyright (C) 2004-2010 Nigel Cunningham (nigel at tuxonice net)
|
|
*
|
|
*/
|
|
|
|
#include <linux/suspend.h>
|
|
#include "tuxonice.h"
|
|
#include "tuxonice_modules.h"
|
|
#include "tuxonice_sysfs.h"
|
|
#include "tuxonice_ui.h"
|
|
|
|
LIST_HEAD(toi_filters);
|
|
LIST_HEAD(toiAllocators);
|
|
|
|
LIST_HEAD(toi_modules);
|
|
EXPORT_SYMBOL_GPL(toi_modules);
|
|
|
|
struct toi_module_ops *toiActiveAllocator;
|
|
EXPORT_SYMBOL_GPL(toiActiveAllocator);
|
|
|
|
static int toi_num_filters;
|
|
int toiNumAllocators, toi_num_modules;
|
|
|
|
/*
|
|
* toi_header_storage_for_modules
|
|
*
|
|
* Returns the amount of space needed to store configuration
|
|
* data needed by the modules prior to copying back the original
|
|
* kernel. We can exclude data for pageset2 because it will be
|
|
* available anyway once the kernel is copied back.
|
|
*/
|
|
long toi_header_storage_for_modules(void)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
int bytes = 0;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled ||
|
|
(this_module->type == WRITER_MODULE && toiActiveAllocator != this_module))
|
|
continue;
|
|
if (this_module->storage_needed) {
|
|
int this = this_module->storage_needed() +
|
|
sizeof(struct toi_module_header) + sizeof(int);
|
|
this_module->header_requested = this;
|
|
bytes += this;
|
|
}
|
|
}
|
|
|
|
/* One more for the empty terminator */
|
|
return bytes + sizeof(struct toi_module_header);
|
|
}
|
|
|
|
void print_toi_header_storage_for_modules(void)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
int bytes = 0;
|
|
|
|
printk(KERN_DEBUG "Header storage:\n");
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled ||
|
|
(this_module->type == WRITER_MODULE && toiActiveAllocator != this_module))
|
|
continue;
|
|
if (this_module->storage_needed) {
|
|
int this = this_module->storage_needed() +
|
|
sizeof(struct toi_module_header) + sizeof(int);
|
|
this_module->header_requested = this;
|
|
bytes += this;
|
|
printk(KERN_DEBUG "+ %16s : %-4d/%d.\n",
|
|
this_module->name, this_module->header_used, this);
|
|
}
|
|
}
|
|
|
|
printk(KERN_DEBUG "+ empty terminator : %zu.\n", sizeof(struct toi_module_header));
|
|
printk(KERN_DEBUG " ====\n");
|
|
printk(KERN_DEBUG " %zu\n", bytes + sizeof(struct toi_module_header));
|
|
}
|
|
EXPORT_SYMBOL_GPL(print_toi_header_storage_for_modules);
|
|
|
|
/*
|
|
* toi_memory_for_modules
|
|
*
|
|
* Returns the amount of memory requested by modules for
|
|
* doing their work during the cycle.
|
|
*/
|
|
|
|
long toi_memory_for_modules(int print_parts)
|
|
{
|
|
long bytes = 0, result;
|
|
struct toi_module_ops *this_module;
|
|
|
|
if (print_parts)
|
|
printk(KERN_INFO "Memory for modules:\n===================\n");
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
int this;
|
|
if (!this_module->enabled)
|
|
continue;
|
|
if (this_module->memory_needed) {
|
|
this = this_module->memory_needed();
|
|
if (print_parts)
|
|
printk(KERN_INFO "%10d bytes (%5ld pages) for "
|
|
"module '%s'.\n", this,
|
|
DIV_ROUND_UP(this, PAGE_SIZE), this_module->name);
|
|
bytes += this;
|
|
}
|
|
}
|
|
|
|
result = DIV_ROUND_UP(bytes, PAGE_SIZE);
|
|
if (print_parts)
|
|
printk(KERN_INFO " => %ld bytes, %ld pages.\n", bytes, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* toi_expected_compression_ratio
|
|
*
|
|
* Returns the compression ratio expected when saving the image.
|
|
*/
|
|
|
|
int toi_expected_compression_ratio(void)
|
|
{
|
|
int ratio = 100;
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled)
|
|
continue;
|
|
if (this_module->expected_compression)
|
|
ratio = ratio * this_module->expected_compression()
|
|
/ 100;
|
|
}
|
|
|
|
return ratio;
|
|
}
|
|
|
|
#ifdef CONFIG_TOI_ENHANCE
|
|
/*
|
|
* toi_actual_compression_ratio
|
|
*
|
|
* Returns the actual compression ratio when saving the image.
|
|
*/
|
|
int toi_actual_compression_ratio(void)
|
|
{
|
|
int ratio = 0;
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled)
|
|
continue;
|
|
if (this_module->actual_compression)
|
|
ratio = this_module->actual_compression();
|
|
}
|
|
|
|
return ratio;
|
|
}
|
|
#endif /* CONFIG_TOI_ENHANCE */
|
|
|
|
|
|
/* toi_find_module_given_dir
|
|
* Functionality : Return a module (if found), given a pointer
|
|
* to its directory name
|
|
*/
|
|
|
|
static struct toi_module_ops *toi_find_module_given_dir(char *name)
|
|
{
|
|
struct toi_module_ops *this_module, *found_module = NULL;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!strcmp(name, this_module->directory)) {
|
|
found_module = this_module;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found_module;
|
|
}
|
|
|
|
/* toi_find_module_given_name
|
|
* Functionality : Return a module (if found), given a pointer
|
|
* to its name
|
|
*/
|
|
|
|
struct toi_module_ops *toi_find_module_given_name(char *name)
|
|
{
|
|
struct toi_module_ops *this_module, *found_module = NULL;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!strcmp(name, this_module->name)) {
|
|
found_module = this_module;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found_module;
|
|
}
|
|
|
|
/*
|
|
* toi_print_module_debug_info
|
|
* Functionality : Get debugging info from modules into a buffer.
|
|
*/
|
|
int toi_print_module_debug_info(char *buffer, int buffer_size)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
int len = 0;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled)
|
|
continue;
|
|
if (this_module->print_debug_info) {
|
|
int result;
|
|
result = this_module->print_debug_info(buffer + len, buffer_size - len);
|
|
len += result;
|
|
}
|
|
}
|
|
|
|
/* Ensure null terminated */
|
|
buffer[buffer_size] = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* toi_register_module
|
|
*
|
|
* Register a module.
|
|
*/
|
|
int toi_register_module(struct toi_module_ops *module)
|
|
{
|
|
int i;
|
|
struct kobject *kobj;
|
|
|
|
module->enabled = 1;
|
|
|
|
if (toi_find_module_given_name(module->name)) {
|
|
printk(KERN_INFO "TuxOnIce: Trying to load module %s,"
|
|
" which is already registered.\n", module->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
switch (module->type) {
|
|
case FILTER_MODULE:
|
|
list_add_tail(&module->type_list, &toi_filters);
|
|
toi_num_filters++;
|
|
break;
|
|
case WRITER_MODULE:
|
|
list_add_tail(&module->type_list, &toiAllocators);
|
|
toiNumAllocators++;
|
|
break;
|
|
case MISC_MODULE:
|
|
case MISC_HIDDEN_MODULE:
|
|
case BIO_ALLOCATOR_MODULE:
|
|
break;
|
|
default:
|
|
printk(KERN_ERR "Hmmm. Module '%s' has an invalid type."
|
|
" It has been ignored.\n", module->name);
|
|
return -EINVAL;
|
|
}
|
|
list_add_tail(&module->module_list, &toi_modules);
|
|
toi_num_modules++;
|
|
|
|
if ((!module->directory && !module->shared_directory) ||
|
|
!module->sysfs_data || !module->num_sysfs_entries)
|
|
return 0;
|
|
|
|
/*
|
|
* Modules may share a directory, but those with shared_dir
|
|
* set must be loaded (via symbol dependencies) after parents
|
|
* and unloaded beforehand.
|
|
*/
|
|
if (module->shared_directory) {
|
|
struct toi_module_ops *shared = toi_find_module_given_dir(module->shared_directory);
|
|
if (!shared) {
|
|
printk(KERN_ERR "TuxOnIce: Module %s wants to share "
|
|
"%s's directory but %s isn't loaded.\n",
|
|
module->name, module->shared_directory, module->shared_directory);
|
|
toi_unregister_module(module);
|
|
return -ENODEV;
|
|
}
|
|
kobj = shared->dir_kobj;
|
|
} else {
|
|
if (!strncmp(module->directory, "[ROOT]", 6))
|
|
kobj = tuxonice_kobj;
|
|
else
|
|
kobj = make_toi_sysdir(module->directory);
|
|
}
|
|
module->dir_kobj = kobj;
|
|
for (i = 0; i < module->num_sysfs_entries; i++) {
|
|
int result = toi_register_sysfs_file(kobj,
|
|
&module->sysfs_data[i]);
|
|
if (result)
|
|
return result;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(toi_register_module);
|
|
|
|
/*
|
|
* toi_unregister_module
|
|
*
|
|
* Remove a module.
|
|
*/
|
|
void toi_unregister_module(struct toi_module_ops *module)
|
|
{
|
|
int i;
|
|
|
|
if (module->dir_kobj)
|
|
for (i = 0; i < module->num_sysfs_entries; i++)
|
|
toi_unregister_sysfs_file(module->dir_kobj, &module->sysfs_data[i]);
|
|
|
|
if (!module->shared_directory && module->directory &&
|
|
strncmp(module->directory, "[ROOT]", 6))
|
|
remove_toi_sysdir(module->dir_kobj);
|
|
|
|
switch (module->type) {
|
|
case FILTER_MODULE:
|
|
list_del(&module->type_list);
|
|
toi_num_filters--;
|
|
break;
|
|
case WRITER_MODULE:
|
|
list_del(&module->type_list);
|
|
toiNumAllocators--;
|
|
if (toiActiveAllocator == module) {
|
|
toiActiveAllocator = NULL;
|
|
clear_toi_state(TOI_CAN_RESUME);
|
|
clear_toi_state(TOI_CAN_HIBERNATE);
|
|
}
|
|
break;
|
|
case MISC_MODULE:
|
|
case MISC_HIDDEN_MODULE:
|
|
case BIO_ALLOCATOR_MODULE:
|
|
break;
|
|
default:
|
|
printk(KERN_ERR "Module '%s' has an invalid type."
|
|
" It has been ignored.\n", module->name);
|
|
return;
|
|
}
|
|
list_del(&module->module_list);
|
|
toi_num_modules--;
|
|
}
|
|
EXPORT_SYMBOL_GPL(toi_unregister_module);
|
|
|
|
/*
|
|
* toi_move_module_tail
|
|
*
|
|
* Rearrange modules when reloading the config.
|
|
*/
|
|
void toi_move_module_tail(struct toi_module_ops *module)
|
|
{
|
|
switch (module->type) {
|
|
case FILTER_MODULE:
|
|
if (toi_num_filters > 1)
|
|
list_move_tail(&module->type_list, &toi_filters);
|
|
break;
|
|
case WRITER_MODULE:
|
|
if (toiNumAllocators > 1)
|
|
list_move_tail(&module->type_list, &toiAllocators);
|
|
break;
|
|
case MISC_MODULE:
|
|
case MISC_HIDDEN_MODULE:
|
|
case BIO_ALLOCATOR_MODULE:
|
|
break;
|
|
default:
|
|
printk(KERN_ERR "Module '%s' has an invalid type."
|
|
" It has been ignored.\n", module->name);
|
|
return;
|
|
}
|
|
if ((toi_num_filters + toiNumAllocators) > 1)
|
|
list_move_tail(&module->module_list, &toi_modules);
|
|
}
|
|
|
|
/*
|
|
* toi_initialise_modules
|
|
*
|
|
* Get ready to do some work!
|
|
*/
|
|
int toi_initialise_modules(int starting_cycle, int early)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
int result;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
this_module->header_requested = 0;
|
|
this_module->header_used = 0;
|
|
if (!this_module->enabled)
|
|
continue;
|
|
if (this_module->early != early)
|
|
continue;
|
|
if (this_module->initialise) {
|
|
result = this_module->initialise(starting_cycle);
|
|
if (result) {
|
|
toi_cleanup_modules(starting_cycle);
|
|
return result;
|
|
}
|
|
this_module->initialised = 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* toi_cleanup_modules
|
|
*
|
|
* Tell modules the work is done.
|
|
*/
|
|
void toi_cleanup_modules(int finishing_cycle)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (!this_module->enabled || !this_module->initialised)
|
|
continue;
|
|
if (this_module->cleanup)
|
|
this_module->cleanup(finishing_cycle);
|
|
this_module->initialised = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* toi_pre_atomic_restore_modules
|
|
*
|
|
* Get ready to do some work!
|
|
*/
|
|
void toi_pre_atomic_restore_modules(struct toi_boot_kernel_data *bkd)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (this_module->enabled && this_module->pre_atomic_restore)
|
|
this_module->pre_atomic_restore(bkd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* toi_post_atomic_restore_modules
|
|
*
|
|
* Get ready to do some work!
|
|
*/
|
|
void toi_post_atomic_restore_modules(struct toi_boot_kernel_data *bkd)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (this_module->enabled && this_module->post_atomic_restore)
|
|
this_module->post_atomic_restore(bkd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* toi_get_next_filter
|
|
*
|
|
* Get the next filter in the pipeline.
|
|
*/
|
|
struct toi_module_ops *toi_get_next_filter(struct toi_module_ops *filter_sought)
|
|
{
|
|
struct toi_module_ops *last_filter = NULL, *this_filter = NULL;
|
|
|
|
list_for_each_entry(this_filter, &toi_filters, type_list) {
|
|
if (!this_filter->enabled)
|
|
continue;
|
|
if ((last_filter == filter_sought) || (!filter_sought))
|
|
return this_filter;
|
|
last_filter = this_filter;
|
|
}
|
|
|
|
return toiActiveAllocator;
|
|
}
|
|
EXPORT_SYMBOL_GPL(toi_get_next_filter);
|
|
|
|
/**
|
|
* toi_show_modules: Printk what support is loaded.
|
|
*/
|
|
void toi_print_modules(void)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
int prev = 0;
|
|
|
|
printk(KERN_INFO "TuxOnIce " TOI_CORE_VERSION ", with support for");
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
if (this_module->type == MISC_HIDDEN_MODULE)
|
|
continue;
|
|
printk("%s %s%s%s", prev ? "," : "",
|
|
this_module->enabled ? "" : "[",
|
|
this_module->name, this_module->enabled ? "" : "]");
|
|
prev = 1;
|
|
}
|
|
|
|
printk(".\n");
|
|
}
|
|
|
|
/* toi_get_modules
|
|
*
|
|
* Take a reference to modules so they can't go away under us.
|
|
*/
|
|
|
|
int toi_get_modules(void)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list) {
|
|
struct toi_module_ops *this_module2;
|
|
|
|
if (try_module_get(this_module->module))
|
|
continue;
|
|
|
|
/* Failed! Reverse gets and return error */
|
|
list_for_each_entry(this_module2, &toi_modules, module_list) {
|
|
if (this_module == this_module2)
|
|
return -EINVAL;
|
|
module_put(this_module2->module);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* toi_put_modules
|
|
*
|
|
* Release our references to modules we used.
|
|
*/
|
|
|
|
void toi_put_modules(void)
|
|
{
|
|
struct toi_module_ops *this_module;
|
|
|
|
list_for_each_entry(this_module, &toi_modules, module_list)
|
|
module_put(this_module->module);
|
|
}
|