/*
 * kernel/power/tuxonice_modules.c
 *
 * Copyright (C) 2004-2015 Nigel Cunningham (nigel at nigelcunningham com au)
 *
 */

#include <linux/suspend.h>
#include <linux/module.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);

struct toi_module_ops *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));
}

/*
 * 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;
}

/* 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;

        if (!hibernation_available())
          return -ENODEV;

        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;
}

/*
 * 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--;
}

/*
 * 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;
}

/**
 * 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);
}