linux/lib/alloc_tag.c
Suren Baghdasaryan e269b5d291 alloc_tag: fix module allocation tags populated area calculation
vm_module_tags_populate() calculation of the populated area assumes that
area starts at a page boundary and therefore when new pages are allocation,
the end of the area is page-aligned as well. If the start of the area is
not page-aligned then allocating a page and incrementing the end of the
area by PAGE_SIZE leads to an area at the end but within the area boundary
which is not populated. Accessing this are will lead to a kernel panic.
Fix the calculation by down-aligning the start of the area and using that
as the location allocated pages are mapped to.

[gehao@kylinos.cn: fix vm_module_tags_populate's KASAN poisoning logic]
  Link: https://lkml.kernel.org/r/20241205170528.81000-1-hao.ge@linux.dev
[gehao@kylinos.cn: fix panic when CONFIG_KASAN enabled and CONFIG_KASAN_VMALLOC not enabled]
  Link: https://lkml.kernel.org/r/20241212072126.134572-1-hao.ge@linux.dev
Link: https://lkml.kernel.org/r/20241130001423.1114965-1-surenb@google.com
Fixes: 0f9b685626 ("alloc_tag: populate memory for module tags as needed")
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
Reported-by: kernel test robot <oliver.sang@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202411132111.6a221562-lkp@intel.com
Acked-by: Yu Zhao <yuzhao@google.com>
Tested-by: Adrian Huang <ahuang12@lenovo.com> 
Cc: David Wang <00107082@163.com>
Cc: Kent Overstreet <kent.overstreet@linux.dev>
Cc: Mike Rapoport (Microsoft) <rppt@kernel.org>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: Sourav Panda <souravpanda@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2024-12-18 19:04:46 -08:00

767 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
#include <linux/alloc_tag.h>
#include <linux/execmem.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/kallsyms.h>
#include <linux/module.h>
#include <linux/page_ext.h>
#include <linux/proc_fs.h>
#include <linux/seq_buf.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#define ALLOCINFO_FILE_NAME "allocinfo"
#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag))
#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME)
#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME)
#ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT
static bool mem_profiling_support = true;
#else
static bool mem_profiling_support;
#endif
static struct codetag_type *alloc_tag_cttype;
DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag);
EXPORT_SYMBOL(_shared_alloc_tag);
DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT,
mem_alloc_profiling_key);
DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed);
struct alloc_tag_kernel_section kernel_tags = { NULL, 0 };
unsigned long alloc_tag_ref_mask;
int alloc_tag_ref_offs;
struct allocinfo_private {
struct codetag_iterator iter;
bool print_header;
};
static void *allocinfo_start(struct seq_file *m, loff_t *pos)
{
struct allocinfo_private *priv;
struct codetag *ct;
loff_t node = *pos;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
m->private = priv;
if (!priv)
return NULL;
priv->print_header = (node == 0);
codetag_lock_module_list(alloc_tag_cttype, true);
priv->iter = codetag_get_ct_iter(alloc_tag_cttype);
while ((ct = codetag_next_ct(&priv->iter)) != NULL && node)
node--;
return ct ? priv : NULL;
}
static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos)
{
struct allocinfo_private *priv = (struct allocinfo_private *)arg;
struct codetag *ct = codetag_next_ct(&priv->iter);
(*pos)++;
if (!ct)
return NULL;
return priv;
}
static void allocinfo_stop(struct seq_file *m, void *arg)
{
struct allocinfo_private *priv = (struct allocinfo_private *)m->private;
if (priv) {
codetag_lock_module_list(alloc_tag_cttype, false);
kfree(priv);
}
}
static void print_allocinfo_header(struct seq_buf *buf)
{
/* Output format version, so we can change it. */
seq_buf_printf(buf, "allocinfo - version: 1.0\n");
seq_buf_printf(buf, "# <size> <calls> <tag info>\n");
}
static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct)
{
struct alloc_tag *tag = ct_to_alloc_tag(ct);
struct alloc_tag_counters counter = alloc_tag_read(tag);
s64 bytes = counter.bytes;
seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls);
codetag_to_text(out, ct);
seq_buf_putc(out, ' ');
seq_buf_putc(out, '\n');
}
static int allocinfo_show(struct seq_file *m, void *arg)
{
struct allocinfo_private *priv = (struct allocinfo_private *)arg;
char *bufp;
size_t n = seq_get_buf(m, &bufp);
struct seq_buf buf;
seq_buf_init(&buf, bufp, n);
if (priv->print_header) {
print_allocinfo_header(&buf);
priv->print_header = false;
}
alloc_tag_to_text(&buf, priv->iter.ct);
seq_commit(m, seq_buf_used(&buf));
return 0;
}
static const struct seq_operations allocinfo_seq_op = {
.start = allocinfo_start,
.next = allocinfo_next,
.stop = allocinfo_stop,
.show = allocinfo_show,
};
size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
{
struct codetag_iterator iter;
struct codetag *ct;
struct codetag_bytes n;
unsigned int i, nr = 0;
if (can_sleep)
codetag_lock_module_list(alloc_tag_cttype, true);
else if (!codetag_trylock_module_list(alloc_tag_cttype))
return 0;
iter = codetag_get_ct_iter(alloc_tag_cttype);
while ((ct = codetag_next_ct(&iter))) {
struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct));
n.ct = ct;
n.bytes = counter.bytes;
for (i = 0; i < nr; i++)
if (n.bytes > tags[i].bytes)
break;
if (i < count) {
nr -= nr == count;
memmove(&tags[i + 1],
&tags[i],
sizeof(tags[0]) * (nr - i));
nr++;
tags[i] = n;
}
}
codetag_lock_module_list(alloc_tag_cttype, false);
return nr;
}
void pgalloc_tag_split(struct folio *folio, int old_order, int new_order)
{
int i;
struct alloc_tag *tag;
unsigned int nr_pages = 1 << new_order;
if (!mem_alloc_profiling_enabled())
return;
tag = pgalloc_tag_get(&folio->page);
if (!tag)
return;
for (i = nr_pages; i < (1 << old_order); i += nr_pages) {
union pgtag_ref_handle handle;
union codetag_ref ref;
if (get_page_tag_ref(folio_page(folio, i), &ref, &handle)) {
/* Set new reference to point to the original tag */
alloc_tag_ref_set(&ref, tag);
update_page_tag_ref(handle, &ref);
put_page_tag_ref(handle);
}
}
}
void pgalloc_tag_swap(struct folio *new, struct folio *old)
{
union pgtag_ref_handle handle_old, handle_new;
union codetag_ref ref_old, ref_new;
struct alloc_tag *tag_old, *tag_new;
tag_old = pgalloc_tag_get(&old->page);
if (!tag_old)
return;
tag_new = pgalloc_tag_get(&new->page);
if (!tag_new)
return;
if (!get_page_tag_ref(&old->page, &ref_old, &handle_old))
return;
if (!get_page_tag_ref(&new->page, &ref_new, &handle_new)) {
put_page_tag_ref(handle_old);
return;
}
/*
* Clear tag references to avoid debug warning when using
* __alloc_tag_ref_set() with non-empty reference.
*/
set_codetag_empty(&ref_old);
set_codetag_empty(&ref_new);
/* swap tags */
__alloc_tag_ref_set(&ref_old, tag_new);
update_page_tag_ref(handle_old, &ref_old);
__alloc_tag_ref_set(&ref_new, tag_old);
update_page_tag_ref(handle_new, &ref_new);
put_page_tag_ref(handle_old);
put_page_tag_ref(handle_new);
}
static void shutdown_mem_profiling(bool remove_file)
{
if (mem_alloc_profiling_enabled())
static_branch_disable(&mem_alloc_profiling_key);
if (!mem_profiling_support)
return;
if (remove_file)
remove_proc_entry(ALLOCINFO_FILE_NAME, NULL);
mem_profiling_support = false;
}
static void __init procfs_init(void)
{
if (!mem_profiling_support)
return;
if (!proc_create_seq(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op)) {
pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
shutdown_mem_profiling(false);
}
}
void __init alloc_tag_sec_init(void)
{
struct alloc_tag *last_codetag;
if (!mem_profiling_support)
return;
if (!static_key_enabled(&mem_profiling_compressed))
return;
kernel_tags.first_tag = (struct alloc_tag *)kallsyms_lookup_name(
SECTION_START(ALLOC_TAG_SECTION_NAME));
last_codetag = (struct alloc_tag *)kallsyms_lookup_name(
SECTION_STOP(ALLOC_TAG_SECTION_NAME));
kernel_tags.count = last_codetag - kernel_tags.first_tag;
/* Check if kernel tags fit into page flags */
if (kernel_tags.count > (1UL << NR_UNUSED_PAGEFLAG_BITS)) {
shutdown_mem_profiling(false); /* allocinfo file does not exist yet */
pr_err("%lu allocation tags cannot be references using %d available page flag bits. Memory allocation profiling is disabled!\n",
kernel_tags.count, NR_UNUSED_PAGEFLAG_BITS);
return;
}
alloc_tag_ref_offs = (LRU_REFS_PGOFF - NR_UNUSED_PAGEFLAG_BITS);
alloc_tag_ref_mask = ((1UL << NR_UNUSED_PAGEFLAG_BITS) - 1);
pr_debug("Memory allocation profiling compression is using %d page flag bits!\n",
NR_UNUSED_PAGEFLAG_BITS);
}
#ifdef CONFIG_MODULES
static struct maple_tree mod_area_mt = MTREE_INIT(mod_area_mt, MT_FLAGS_ALLOC_RANGE);
static struct vm_struct *vm_module_tags;
/* A dummy object used to indicate an unloaded module */
static struct module unloaded_mod;
/* A dummy object used to indicate a module prepended area */
static struct module prepend_mod;
struct alloc_tag_module_section module_tags;
static inline unsigned long alloc_tag_align(unsigned long val)
{
if (!static_key_enabled(&mem_profiling_compressed)) {
/* No alignment requirements when we are not indexing the tags */
return val;
}
if (val % sizeof(struct alloc_tag) == 0)
return val;
return ((val / sizeof(struct alloc_tag)) + 1) * sizeof(struct alloc_tag);
}
static bool ensure_alignment(unsigned long align, unsigned int *prepend)
{
if (!static_key_enabled(&mem_profiling_compressed)) {
/* No alignment requirements when we are not indexing the tags */
return true;
}
/*
* If alloc_tag size is not a multiple of required alignment, tag
* indexing does not work.
*/
if (!IS_ALIGNED(sizeof(struct alloc_tag), align))
return false;
/* Ensure prepend consumes multiple of alloc_tag-sized blocks */
if (*prepend)
*prepend = alloc_tag_align(*prepend);
return true;
}
static inline bool tags_addressable(void)
{
unsigned long tag_idx_count;
if (!static_key_enabled(&mem_profiling_compressed))
return true; /* with page_ext tags are always addressable */
tag_idx_count = CODETAG_ID_FIRST + kernel_tags.count +
module_tags.size / sizeof(struct alloc_tag);
return tag_idx_count < (1UL << NR_UNUSED_PAGEFLAG_BITS);
}
static bool needs_section_mem(struct module *mod, unsigned long size)
{
if (!mem_profiling_support)
return false;
return size >= sizeof(struct alloc_tag);
}
static struct alloc_tag *find_used_tag(struct alloc_tag *from, struct alloc_tag *to)
{
while (from <= to) {
struct alloc_tag_counters counter;
counter = alloc_tag_read(from);
if (counter.bytes)
return from;
from++;
}
return NULL;
}
/* Called with mod_area_mt locked */
static void clean_unused_module_areas_locked(void)
{
MA_STATE(mas, &mod_area_mt, 0, module_tags.size);
struct module *val;
mas_for_each(&mas, val, module_tags.size) {
if (val != &unloaded_mod)
continue;
/* Release area if all tags are unused */
if (!find_used_tag((struct alloc_tag *)(module_tags.start_addr + mas.index),
(struct alloc_tag *)(module_tags.start_addr + mas.last)))
mas_erase(&mas);
}
}
/* Called with mod_area_mt locked */
static bool find_aligned_area(struct ma_state *mas, unsigned long section_size,
unsigned long size, unsigned int prepend, unsigned long align)
{
bool cleanup_done = false;
repeat:
/* Try finding exact size and hope the start is aligned */
if (!mas_empty_area(mas, 0, section_size - 1, prepend + size)) {
if (IS_ALIGNED(mas->index + prepend, align))
return true;
/* Try finding larger area to align later */
mas_reset(mas);
if (!mas_empty_area(mas, 0, section_size - 1,
size + prepend + align - 1))
return true;
}
/* No free area, try cleanup stale data and repeat the search once */
if (!cleanup_done) {
clean_unused_module_areas_locked();
cleanup_done = true;
mas_reset(mas);
goto repeat;
}
return false;
}
static int vm_module_tags_populate(void)
{
unsigned long phys_end = ALIGN_DOWN(module_tags.start_addr, PAGE_SIZE) +
(vm_module_tags->nr_pages << PAGE_SHIFT);
unsigned long new_end = module_tags.start_addr + module_tags.size;
if (phys_end < new_end) {
struct page **next_page = vm_module_tags->pages + vm_module_tags->nr_pages;
unsigned long old_shadow_end = ALIGN(phys_end, MODULE_ALIGN);
unsigned long new_shadow_end = ALIGN(new_end, MODULE_ALIGN);
unsigned long more_pages;
unsigned long nr;
more_pages = ALIGN(new_end - phys_end, PAGE_SIZE) >> PAGE_SHIFT;
nr = alloc_pages_bulk_array_node(GFP_KERNEL | __GFP_NOWARN,
NUMA_NO_NODE, more_pages, next_page);
if (nr < more_pages ||
vmap_pages_range(phys_end, phys_end + (nr << PAGE_SHIFT), PAGE_KERNEL,
next_page, PAGE_SHIFT) < 0) {
/* Clean up and error out */
for (int i = 0; i < nr; i++)
__free_page(next_page[i]);
return -ENOMEM;
}
vm_module_tags->nr_pages += nr;
/*
* Kasan allocates 1 byte of shadow for every 8 bytes of data.
* When kasan_alloc_module_shadow allocates shadow memory,
* its unit of allocation is a page.
* Therefore, here we need to align to MODULE_ALIGN.
*/
if (old_shadow_end < new_shadow_end)
kasan_alloc_module_shadow((void *)old_shadow_end,
new_shadow_end - old_shadow_end,
GFP_KERNEL);
}
/*
* Mark the pages as accessible, now that they are mapped.
* With hardware tag-based KASAN, marking is skipped for
* non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
*/
kasan_unpoison_vmalloc((void *)module_tags.start_addr,
new_end - module_tags.start_addr,
KASAN_VMALLOC_PROT_NORMAL);
return 0;
}
static void *reserve_module_tags(struct module *mod, unsigned long size,
unsigned int prepend, unsigned long align)
{
unsigned long section_size = module_tags.end_addr - module_tags.start_addr;
MA_STATE(mas, &mod_area_mt, 0, section_size - 1);
unsigned long offset;
void *ret = NULL;
/* If no tags return error */
if (size < sizeof(struct alloc_tag))
return ERR_PTR(-EINVAL);
/*
* align is always power of 2, so we can use IS_ALIGNED and ALIGN.
* align 0 or 1 means no alignment, to simplify set to 1.
*/
if (!align)
align = 1;
if (!ensure_alignment(align, &prepend)) {
shutdown_mem_profiling(true);
pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n",
mod->name, align);
return ERR_PTR(-EINVAL);
}
mas_lock(&mas);
if (!find_aligned_area(&mas, section_size, size, prepend, align)) {
ret = ERR_PTR(-ENOMEM);
goto unlock;
}
/* Mark found area as reserved */
offset = mas.index;
offset += prepend;
offset = ALIGN(offset, align);
if (offset != mas.index) {
unsigned long pad_start = mas.index;
mas.last = offset - 1;
mas_store(&mas, &prepend_mod);
if (mas_is_err(&mas)) {
ret = ERR_PTR(xa_err(mas.node));
goto unlock;
}
mas.index = offset;
mas.last = offset + size - 1;
mas_store(&mas, mod);
if (mas_is_err(&mas)) {
mas.index = pad_start;
mas_erase(&mas);
ret = ERR_PTR(xa_err(mas.node));
}
} else {
mas.last = offset + size - 1;
mas_store(&mas, mod);
if (mas_is_err(&mas))
ret = ERR_PTR(xa_err(mas.node));
}
unlock:
mas_unlock(&mas);
if (IS_ERR(ret))
return ret;
if (module_tags.size < offset + size) {
int grow_res;
module_tags.size = offset + size;
if (mem_alloc_profiling_enabled() && !tags_addressable()) {
shutdown_mem_profiling(true);
pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n",
mod->name, NR_UNUSED_PAGEFLAG_BITS);
}
grow_res = vm_module_tags_populate();
if (grow_res) {
shutdown_mem_profiling(true);
pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n",
mod->name);
return ERR_PTR(grow_res);
}
}
return (struct alloc_tag *)(module_tags.start_addr + offset);
}
static void release_module_tags(struct module *mod, bool used)
{
MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size);
struct alloc_tag *tag;
struct module *val;
mas_lock(&mas);
mas_for_each_rev(&mas, val, 0)
if (val == mod)
break;
if (!val) /* module not found */
goto out;
if (!used)
goto release_area;
/* Find out if the area is used */
tag = find_used_tag((struct alloc_tag *)(module_tags.start_addr + mas.index),
(struct alloc_tag *)(module_tags.start_addr + mas.last));
if (tag) {
struct alloc_tag_counters counter = alloc_tag_read(tag);
pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n",
tag->ct.filename, tag->ct.lineno, tag->ct.modname,
tag->ct.function, counter.bytes);
} else {
used = false;
}
release_area:
mas_store(&mas, used ? &unloaded_mod : NULL);
val = mas_prev_range(&mas, 0);
if (val == &prepend_mod)
mas_store(&mas, NULL);
out:
mas_unlock(&mas);
}
static void replace_module(struct module *mod, struct module *new_mod)
{
MA_STATE(mas, &mod_area_mt, 0, module_tags.size);
struct module *val;
mas_lock(&mas);
mas_for_each(&mas, val, module_tags.size) {
if (val != mod)
continue;
mas_store_gfp(&mas, new_mod, GFP_KERNEL);
break;
}
mas_unlock(&mas);
}
static int __init alloc_mod_tags_mem(void)
{
/* Map space to copy allocation tags */
vm_module_tags = execmem_vmap(MODULE_ALLOC_TAG_VMAP_SIZE);
if (!vm_module_tags) {
pr_err("Failed to map %lu bytes for module allocation tags\n",
MODULE_ALLOC_TAG_VMAP_SIZE);
module_tags.start_addr = 0;
return -ENOMEM;
}
vm_module_tags->pages = kmalloc_array(get_vm_area_size(vm_module_tags) >> PAGE_SHIFT,
sizeof(struct page *), GFP_KERNEL | __GFP_ZERO);
if (!vm_module_tags->pages) {
free_vm_area(vm_module_tags);
return -ENOMEM;
}
module_tags.start_addr = (unsigned long)vm_module_tags->addr;
module_tags.end_addr = module_tags.start_addr + MODULE_ALLOC_TAG_VMAP_SIZE;
/* Ensure the base is alloc_tag aligned when required for indexing */
module_tags.start_addr = alloc_tag_align(module_tags.start_addr);
return 0;
}
static void __init free_mod_tags_mem(void)
{
int i;
module_tags.start_addr = 0;
for (i = 0; i < vm_module_tags->nr_pages; i++)
__free_page(vm_module_tags->pages[i]);
kfree(vm_module_tags->pages);
free_vm_area(vm_module_tags);
}
#else /* CONFIG_MODULES */
static inline int alloc_mod_tags_mem(void) { return 0; }
static inline void free_mod_tags_mem(void) {}
#endif /* CONFIG_MODULES */
/* See: Documentation/mm/allocation-profiling.rst */
static int __init setup_early_mem_profiling(char *str)
{
bool compressed = false;
bool enable;
if (!str || !str[0])
return -EINVAL;
if (!strncmp(str, "never", 5)) {
enable = false;
mem_profiling_support = false;
pr_info("Memory allocation profiling is disabled!\n");
} else {
char *token = strsep(&str, ",");
if (kstrtobool(token, &enable))
return -EINVAL;
if (str) {
if (strcmp(str, "compressed"))
return -EINVAL;
compressed = true;
}
mem_profiling_support = true;
pr_info("Memory allocation profiling is enabled %s compression and is turned %s!\n",
compressed ? "with" : "without", enable ? "on" : "off");
}
if (enable != mem_alloc_profiling_enabled()) {
if (enable)
static_branch_enable(&mem_alloc_profiling_key);
else
static_branch_disable(&mem_alloc_profiling_key);
}
if (compressed != static_key_enabled(&mem_profiling_compressed)) {
if (compressed)
static_branch_enable(&mem_profiling_compressed);
else
static_branch_disable(&mem_profiling_compressed);
}
return 0;
}
early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling);
static __init bool need_page_alloc_tagging(void)
{
if (static_key_enabled(&mem_profiling_compressed))
return false;
return mem_profiling_support;
}
static __init void init_page_alloc_tagging(void)
{
}
struct page_ext_operations page_alloc_tagging_ops = {
.size = sizeof(union codetag_ref),
.need = need_page_alloc_tagging,
.init = init_page_alloc_tagging,
};
EXPORT_SYMBOL(page_alloc_tagging_ops);
#ifdef CONFIG_SYSCTL
static struct ctl_table memory_allocation_profiling_sysctls[] = {
{
.procname = "mem_profiling",
.data = &mem_alloc_profiling_key,
#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG
.mode = 0444,
#else
.mode = 0644,
#endif
.proc_handler = proc_do_static_key,
},
};
static void __init sysctl_init(void)
{
if (!mem_profiling_support)
memory_allocation_profiling_sysctls[0].mode = 0444;
register_sysctl_init("vm", memory_allocation_profiling_sysctls);
}
#else /* CONFIG_SYSCTL */
static inline void sysctl_init(void) {}
#endif /* CONFIG_SYSCTL */
static int __init alloc_tag_init(void)
{
const struct codetag_type_desc desc = {
.section = ALLOC_TAG_SECTION_NAME,
.tag_size = sizeof(struct alloc_tag),
#ifdef CONFIG_MODULES
.needs_section_mem = needs_section_mem,
.alloc_section_mem = reserve_module_tags,
.free_section_mem = release_module_tags,
.module_replaced = replace_module,
#endif
};
int res;
res = alloc_mod_tags_mem();
if (res)
return res;
alloc_tag_cttype = codetag_register_type(&desc);
if (IS_ERR(alloc_tag_cttype)) {
free_mod_tags_mem();
return PTR_ERR(alloc_tag_cttype);
}
sysctl_init();
procfs_init();
return 0;
}
module_init(alloc_tag_init);