| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * debug_kinfo.c - backup kernel information for bootloader usage | |
| * | |
| * Copyright 2002 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation | |
| * Copyright 2021 Google LLC | |
| */ | |
| #include <linux/platform_device.h> | |
| #include <linux/kallsyms.h> | |
| #include <linux/vmalloc.h> | |
| #include <linux/module.h> | |
| #include <linux/of_address.h> | |
| #include <linux/of_reserved_mem.h> | |
| #include <linux/pgtable.h> | |
| #include <asm/module.h> | |
| #include "debug_kinfo.h" | |
| /* | |
| * These will be re-linked against their real values | |
| * during the second link stage. | |
| */ | |
| extern const int kallsyms_offsets[] __weak; | |
| extern const u8 kallsyms_names[] __weak; | |
| extern const u8 kallsyms_seqs_of_names[] __weak; | |
| /* | |
| * Tell the compiler that the count isn't in the small data section if the arch | |
| * has one (eg: FRV). | |
| */ | |
| extern const unsigned int kallsyms_num_syms __weak | |
| __section(".rodata"); | |
| extern const unsigned long kallsyms_relative_base __weak | |
| __section(".rodata"); | |
| extern const u8 kallsyms_token_table[] __weak; | |
| extern const u16 kallsyms_token_index[] __weak; | |
| extern const unsigned int kallsyms_markers[] __weak; | |
| static void *all_info_addr; | |
| static u32 all_info_size; | |
| static void update_kernel_all_info(struct kernel_all_info *all_info) | |
| { | |
| int index; | |
| struct kernel_info *info; | |
| u32 *checksum_info; | |
| all_info->magic_number = DEBUG_KINFO_MAGIC; | |
| all_info->combined_checksum = 0; | |
| info = &(all_info->info); | |
| checksum_info = (u32 *)info; | |
| for (index = 0; index < sizeof(*info) / sizeof(u32); index++) | |
| all_info->combined_checksum ^= checksum_info[index]; | |
| } | |
| static int build_info_set(const char *str, const struct kernel_param *kp) | |
| { | |
| struct kernel_all_info *all_info; | |
| size_t build_info_size; | |
| int ret = 0; | |
| if (all_info_addr == 0 || all_info_size == 0) { | |
| ret = -EPERM; | |
| goto Exit; | |
| } | |
| all_info = (struct kernel_all_info *)all_info_addr; | |
| build_info_size = sizeof(all_info->info.build_info); | |
| memcpy(&all_info->info.build_info, str, min(build_info_size - 1, strlen(str))); | |
| update_kernel_all_info(all_info); | |
| if (strlen(str) > build_info_size) { | |
| pr_warn("%s: Build info buffer (len: %zd) can't hold entire string '%s'\n", | |
| __func__, build_info_size, str); | |
| ret = -ENOMEM; | |
| } | |
| Exit: | |
| return ret; | |
| } | |
| static const struct kernel_param_ops build_info_op = { | |
| .set = build_info_set, | |
| }; | |
| module_param_cb(build_info, &build_info_op, NULL, 0200); | |
| MODULE_PARM_DESC(build_info, "Write build info to field 'build_info' of debug kinfo."); | |
| static int debug_kinfo_probe(struct platform_device *pdev) | |
| { | |
| struct device_node *mem_region; | |
| struct reserved_mem *rmem; | |
| struct kernel_all_info *all_info; | |
| struct kernel_info *info; | |
| mem_region = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); | |
| if (!mem_region) { | |
| dev_warn(&pdev->dev, "no such memory-region\n"); | |
| return -ENODEV; | |
| } | |
| rmem = of_reserved_mem_lookup(mem_region); | |
| if (!rmem) { | |
| dev_warn(&pdev->dev, "no such reserved mem of node name %s\n", | |
| pdev->dev.of_node->name); | |
| return -ENODEV; | |
| } | |
| /* Need to wait for reserved memory to be mapped */ | |
| if (!rmem->priv) { | |
| return -EPROBE_DEFER; | |
| } | |
| if (!rmem->base || !rmem->size) { | |
| dev_warn(&pdev->dev, "unexpected reserved memory\n"); | |
| return -EINVAL; | |
| } | |
| if (rmem->size < sizeof(struct kernel_all_info)) { | |
| dev_warn(&pdev->dev, "unexpected reserved memory size\n"); | |
| return -EINVAL; | |
| } | |
| all_info_addr = rmem->priv; | |
| all_info_size = rmem->size; | |
| memset(all_info_addr, 0, sizeof(struct kernel_all_info)); | |
| all_info = (struct kernel_all_info *)all_info_addr; | |
| info = &(all_info->info); | |
| info->enabled_all = IS_ENABLED(CONFIG_KALLSYMS_ALL); | |
| info->enabled_absolute_percpu = IS_ENABLED(CONFIG_KALLSYMS_ABSOLUTE_PERCPU); | |
| info->enabled_cfi_clang = IS_ENABLED(CONFIG_CFI); | |
| info->num_syms = kallsyms_num_syms; | |
| info->name_len = KSYM_NAME_LEN; | |
| info->bit_per_long = BITS_PER_LONG; | |
| info->module_name_len = MODULE_NAME_LEN; | |
| info->symbol_len = KSYM_SYMBOL_LEN; | |
| info->_relative_pa = (u64)__pa_symbol((volatile void *)kallsyms_relative_base); | |
| info->_offsets_pa = (u64)__pa_symbol((volatile void *)kallsyms_offsets); | |
| info->_text_pa = (u64)__pa_symbol(_text); | |
| info->_stext_pa = (u64)__pa_symbol(_stext); | |
| info->_etext_pa = (u64)__pa_symbol(_etext); | |
| info->_sinittext_pa = (u64)__pa_symbol(_sinittext); | |
| info->_einittext_pa = (u64)__pa_symbol(_einittext); | |
| info->_end_pa = (u64)__pa_symbol(_end); | |
| info->_names_pa = (u64)__pa_symbol((volatile void *)kallsyms_names); | |
| info->_token_table_pa = (u64)__pa_symbol((volatile void *)kallsyms_token_table); | |
| info->_token_index_pa = (u64)__pa_symbol((volatile void *)kallsyms_token_index); | |
| info->_markers_pa = (u64)__pa_symbol((volatile void *)kallsyms_markers); | |
| info->_seqs_of_names_pa = (u64)__pa_symbol((volatile void *)kallsyms_seqs_of_names); | |
| info->thread_size = THREAD_SIZE; | |
| info->swapper_pg_dir_pa = (u64)__pa_symbol(swapper_pg_dir); | |
| strscpy(info->last_uts_release, init_utsname()->release, sizeof(info->last_uts_release)); | |
| info->enabled_modules_tree_lookup = IS_ENABLED(CONFIG_MODULES_TREE_LOOKUP); | |
| info->mod_mem_offset = offsetof(struct module, mem); | |
| info->mod_kallsyms_offset = offsetof(struct module, kallsyms); | |
| update_kernel_all_info(all_info); | |
| return 0; | |
| } | |
| static const struct of_device_id debug_kinfo_of_match[] = { | |
| { .compatible = "google,debug-kinfo" }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(of, debug_kinfo_of_match); | |
| static struct platform_driver debug_kinfo_driver = { | |
| .probe = debug_kinfo_probe, | |
| .driver = { | |
| .name = "debug-kinfo", | |
| .of_match_table = of_match_ptr(debug_kinfo_of_match), | |
| }, | |
| }; | |
| module_platform_driver(debug_kinfo_driver); | |
| /* | |
| * For type visibility | |
| */ | |
| const struct kernel_all_info *const ANDROID_GKI_struct_kernel_all_info; | |
| EXPORT_SYMBOL_GPL(ANDROID_GKI_struct_kernel_all_info); | |
| MODULE_AUTHOR("Jone Chou <jonechou@google.com>"); | |
| MODULE_DESCRIPTION("Debug Kinfo Driver"); | |
| MODULE_LICENSE("GPL v2"); |