/* * kernel/power/tuxonice_modules.c * * Copyright (C) 2004-2015 Nigel Cunningham (nigel at nigelcunningham com au) * */ #include #include #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); }