/* 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 #include #include #include #include #include #include #include #include #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);