| // 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 unsigned long kallsyms_addresses[] __weak; |
| extern const int kallsyms_offsets[] __weak; |
| extern const u8 kallsyms_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_base_relative = IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE); |
| info->enabled_absolute_percpu = IS_ENABLED(CONFIG_KALLSYMS_ABSOLUTE_PERCPU); |
| info->enabled_cfi_clang = IS_ENABLED(CONFIG_CFI_CLANG); |
| 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; |
| if (!info->enabled_base_relative) |
| info->_addresses_pa = (u64)__pa_symbol((volatile void *)kallsyms_addresses); |
| else { |
| info->_relative_pa = (u64)__pa_symbol((volatile void *)kallsyms_relative_base); |
| info->_offsets_pa = (u64)__pa_symbol((volatile void *)kallsyms_offsets); |
| } |
| 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->thread_size = THREAD_SIZE; |
| info->swapper_pg_dir_pa = (u64)__pa_symbol(swapper_pg_dir); |
| strlcpy(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_core_layout_offset = offsetof(struct module, core_layout); |
| info->mod_init_layout_offset = offsetof(struct module, init_layout); |
| info->mod_kallsyms_offset = offsetof(struct module, kallsyms); |
| #if defined(CONFIG_RANDOMIZE_BASE) && defined(MODULES_VSIZE) |
| info->module_start_va = module_alloc_base; |
| info->module_end_va = info->module_start_va + MODULES_VSIZE; |
| #elif defined(CONFIG_MODULES) && defined(MODULES_VADDR) |
| info->module_start_va = MODULES_VADDR; |
| info->module_end_va = MODULES_END; |
| #else |
| info->module_start_va = VMALLOC_START; |
| info->module_end_va = VMALLOC_END; |
| #endif |
| 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); |
| |
| MODULE_AUTHOR("Jone Chou <jonechou@google.com>"); |
| MODULE_DESCRIPTION("Debug Kinfo Driver"); |
| MODULE_LICENSE("GPL v2"); |