| /* Copyright (c) 2014, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/init.h> |
| #include <linux/err.h> |
| #include <linux/debugfs.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <soc/qcom/subsystem_notif.h> |
| #include <soc/qcom/hvc.h> |
| |
| #include "peripheral-loader.h" |
| |
| #define hyp_dbg_err(fmt, ...) \ |
| pr_err("%s: " fmt, "hyp-debug", ##__VA_ARGS__) |
| #define hyp_dbg_info(fmt, ...) \ |
| pr_info("%s: " fmt, "hyp-debug", ##__VA_ARGS__) |
| |
| #define HVC_FN_DBG_MAP_RANGE HVC_FN_SIP(1) |
| #define HVC_FN_DBG_UNMAP_RANGE HVC_FN_SIP(2) |
| |
| #define MEM_PERM_EXECUTE BIT(0) |
| #define MEM_PERM_WRITE BIT(1) |
| #define MEM_PERM_READ BIT(2) |
| |
| #define MEM_CACHE_nGnRnE 0x0 |
| #define MEM_CACHE_nGnRE 0x1 |
| #define MEM_CACHE_nGRE 0x2 |
| #define MEM_CACHE_GRE 0x3 |
| #define MEM_CACHE_ONC_INC 0x5 |
| #define MEM_CACHE_ONC_IWT 0x6 |
| #define MEM_CACHE_ONC_IWB 0x7 |
| #define MEM_CACHE_OWT_INC 0x9 |
| #define MEM_CACHE_OWT_IWT 0xA |
| #define MEM_CACHE_OWT_IWB 0xB |
| #define MEM_CACHE_OWB_INC 0xD |
| #define MEM_CACHE_OWB_IWT 0xE |
| #define MEM_CACHE_OWB_IWB 0xF |
| |
| #define MEM_SHARE_NS 0x0 |
| #define MEM_SHARE_OS 0x2 |
| #define MEM_SHARE_IS 0x3 |
| |
| static u64 mem_addr, mem_size, mem_perm_attr, mem_cache_attr, mem_share_attr; |
| |
| static int hyp_debug_mem_addr_get(void *data, u64 *val) |
| { |
| *val = mem_addr; |
| return 0; |
| } |
| |
| static int hyp_debug_mem_addr_set(void *data, u64 val) |
| { |
| mem_addr = val; |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_addr_fops, hyp_debug_mem_addr_get, |
| hyp_debug_mem_addr_set, "%llu\n"); |
| |
| static int hyp_debug_mem_size_get(void *data, u64 *val) |
| { |
| *val = mem_size; |
| return 0; |
| } |
| |
| static int hyp_debug_mem_size_set(void *data, u64 val) |
| { |
| mem_size = val; |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_size_fops, hyp_debug_mem_size_get, |
| hyp_debug_mem_size_set, "%llu\n"); |
| |
| static int hyp_debug_mem_perm_attr_get(void *data, u64 *val) |
| { |
| *val = mem_perm_attr; |
| return 0; |
| } |
| |
| static int hyp_debug_mem_perm_attr_set(void *data, u64 val) |
| { |
| mem_perm_attr = val; |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_perm_attr_fops, |
| hyp_debug_mem_perm_attr_get, |
| hyp_debug_mem_perm_attr_set, "%llu\n"); |
| |
| static int hyp_debug_mem_cache_attr_get(void *data, u64 *val) |
| { |
| *val = mem_cache_attr; |
| return 0; |
| } |
| |
| static int hyp_debug_mem_cache_attr_set(void *data, u64 val) |
| { |
| mem_cache_attr = val; |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_cache_attr_fops, |
| hyp_debug_mem_cache_attr_get, |
| hyp_debug_mem_cache_attr_set, "%llu\n"); |
| |
| static int hyp_debug_mem_share_attr_get(void *data, u64 *val) |
| { |
| *val = mem_share_attr; |
| return 0; |
| } |
| |
| static int hyp_debug_mem_share_attr_set(void *data, u64 val) |
| { |
| mem_share_attr = val; |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_share_attr_fops, |
| hyp_debug_mem_share_attr_get, |
| hyp_debug_mem_share_attr_set, "%llu\n"); |
| |
| static int hyp_debug_mem_map_set(void *data, u64 val) |
| { |
| struct hvc_desc desc = { {0}, {0} }; |
| int ret; |
| |
| desc.arg[0] = mem_addr; |
| desc.arg[1] = mem_size; |
| desc.arg[2] = mem_perm_attr; |
| desc.arg[3] = mem_cache_attr; |
| desc.arg[4] = mem_share_attr; |
| ret = hvc(HVC_FN_DBG_MAP_RANGE, &desc); |
| if (ret) |
| hyp_dbg_err("user specified hvc map range failed: %d\n", ret); |
| |
| return ret; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_map_fops, NULL, hyp_debug_mem_map_set, |
| "%llu\n"); |
| |
| static int hyp_debug_mem_unmap_set(void *data, u64 val) |
| { |
| struct hvc_desc desc = { {0}, {0} }; |
| int ret; |
| |
| desc.arg[0] = mem_addr; |
| desc.arg[1] = mem_size; |
| ret = hvc(HVC_FN_DBG_UNMAP_RANGE, &desc); |
| if (ret) |
| hyp_dbg_err("user specified hvc unmap range failed: %d\n", ret); |
| |
| return ret; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_unmap_fops, NULL, hyp_debug_mem_unmap_set, |
| "%llu\n"); |
| |
| struct restart_notifier_block { |
| struct pil_image_info __iomem *pil_info; |
| struct notifier_block nb; |
| }; |
| |
| static struct restart_notifier_block *restart_nbs; |
| |
| static int restart_notifier_cb(struct notifier_block *this, unsigned long code, |
| void *data) |
| { |
| struct restart_notifier_block *restart_nb; |
| struct hvc_desc desc = { {0}, {0} }; |
| u64 addr; |
| u32 size; |
| int ret; |
| |
| restart_nb = container_of(this, struct restart_notifier_block, nb); |
| |
| switch (code) { |
| case SUBSYS_AFTER_POWERUP: |
| memcpy_fromio(&addr, &restart_nb->pil_info->start, sizeof(u64)); |
| size = readl_relaxed(&restart_nb->pil_info->size); |
| desc.arg[0] = addr; |
| desc.arg[1] = size; |
| ret = hvc(HVC_FN_DBG_UNMAP_RANGE, &desc); |
| if (ret) |
| hyp_dbg_err("subsys hvc unmap range failed: %lu %d\n", |
| code, ret); |
| break; |
| case SUBSYS_BEFORE_SHUTDOWN: |
| memcpy_fromio(&addr, &restart_nb->pil_info->start, sizeof(u64)); |
| size = readl_relaxed(&restart_nb->pil_info->size); |
| desc.arg[0] = addr; |
| desc.arg[1] = size; |
| desc.arg[2] = MEM_PERM_EXECUTE | MEM_PERM_WRITE | MEM_PERM_READ; |
| desc.arg[3] = MEM_CACHE_OWB_IWB; |
| desc.arg[4] = MEM_SHARE_NS; |
| ret = hvc(HVC_FN_DBG_MAP_RANGE, &desc); |
| if (ret) |
| hyp_dbg_err("subsys hvc map range failed: %lu %d\n", |
| code, ret); |
| break; |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int __init hyp_debug_init(void) |
| { |
| struct device_node *np; |
| struct resource res; |
| struct dentry *debugfs_dir, *debugfs_file; |
| char subsys_name[FIELD_SIZEOF(struct pil_image_info, name)]; |
| void __iomem *pil_info_base, __iomem *addr; |
| void *handle; |
| u32 nr_restart_nb; |
| int i, ret; |
| |
| np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-pil"); |
| if (!np) { |
| hyp_dbg_err("pil imem DT node does not exist\n"); |
| return -ENODEV; |
| } |
| |
| ret = of_address_to_resource(np, 0, &res); |
| if (ret) |
| return ret; |
| |
| pil_info_base = ioremap(res.start, resource_size(&res)); |
| if (!pil_info_base) { |
| hyp_dbg_err("pil info imem base offset mapping failed\n"); |
| return -ENOMEM; |
| } |
| |
| nr_restart_nb = resource_size(&res) / sizeof(struct pil_image_info); |
| restart_nbs = kzalloc(nr_restart_nb * |
| sizeof(struct restart_notifier_block), GFP_KERNEL); |
| if (!restart_nbs) { |
| ret = -ENOMEM; |
| hyp_dbg_err("restart notifiers allocation failed\n"); |
| goto err0; |
| } |
| |
| for (i = 0; i < nr_restart_nb; i++) { |
| addr = pil_info_base + sizeof(struct pil_image_info) * i; |
| restart_nbs[i].pil_info = (struct pil_image_info __iomem *)addr; |
| |
| memcpy_fromio(subsys_name, restart_nbs[i].pil_info->name, |
| sizeof(restart_nbs[i].pil_info->name)); |
| if (subsys_name[0] == '\0') |
| break; |
| |
| restart_nbs[i].nb.notifier_call = restart_notifier_cb; |
| handle = subsys_notif_register_notifier(subsys_name, |
| &restart_nbs[i].nb); |
| if (IS_ERR_OR_NULL(handle)) { |
| ret = PTR_ERR(handle); |
| hyp_dbg_err("subsys notif register %d failed: %d\n", i, |
| ret); |
| goto err1; |
| } |
| } |
| |
| mem_perm_attr = MEM_PERM_EXECUTE | MEM_PERM_WRITE | MEM_PERM_READ; |
| mem_cache_attr = MEM_CACHE_OWB_IWB; |
| mem_share_attr = MEM_SHARE_NS; |
| |
| debugfs_dir = debugfs_create_dir("hyp_debug", NULL); |
| if (IS_ERR_OR_NULL(debugfs_dir)) { |
| ret = PTR_ERR(debugfs_dir); |
| goto err1; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_addr", S_IRUGO, debugfs_dir, |
| NULL, &hyp_debug_mem_addr_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_size", S_IRUGO, debugfs_dir, |
| NULL, &hyp_debug_mem_size_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_perm_attr", S_IRUGO, |
| debugfs_dir, NULL, |
| &hyp_debug_mem_perm_attr_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_cache_attr", S_IRUGO, |
| debugfs_dir, NULL, |
| &hyp_debug_mem_cache_attr_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_share_attr", S_IRUGO, |
| debugfs_dir, NULL, |
| &hyp_debug_mem_share_attr_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_map", S_IRUGO, debugfs_dir, |
| NULL, &hyp_debug_mem_map_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| debugfs_file = debugfs_create_file("mem_unmap", S_IRUGO, debugfs_dir, |
| NULL, &hyp_debug_mem_unmap_fops); |
| if (IS_ERR_OR_NULL(debugfs_file)) { |
| ret = PTR_ERR(debugfs_file); |
| goto err2; |
| } |
| |
| hyp_dbg_info("MSM Hyp Debug initialized\n"); |
| return 0; |
| err2: |
| debugfs_remove_recursive(debugfs_dir); |
| err1: |
| for (i--; i >= 0; i--) |
| subsys_notif_unregister_notifier(handle, &restart_nbs[i].nb); |
| kfree(restart_nbs); |
| err0: |
| iounmap(pil_info_base); |
| return ret; |
| } |
| late_initcall(hyp_debug_init); |