android_kernel_samsung_msm8976/drivers/soc/qcom/hyp-debug.c

343 lines
8.8 KiB
C

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