blob: 35d3cb480532890bd7fdbd487490588dfb69a06d [file] [edit]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2018 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/keydebug.h>
#include <soc/google/debug-snapshot.h>
#include <soc/google/exynos-adv-tracer.h>
#include <soc/google/exynos-pmu-if.h>
#if IS_ENABLED(CONFIG_EXYNOS_CPUIDLE)
#include <soc/google/exynos-cpupm.h>
#endif
#include <dt-bindings/soc/google/debug-snapshot-def.h>
enum s2d_ipc_cmd {
eS2D_IPC_CMD_SET_ALL_BLK = 1,
eS2D_IPC_CMD_GET_ALL_BLK,
eS2D_IPC_CMD_SET_ENABLE,
eS2D_IPC_CMD_GET_ENABLE,
eS2D_IPC_CMD_SET_BLK,
eS2D_IPC_CMD_SET_DISABLE_GPR,
eS2D_IPC_CMD_GET_DISABLE_GPR,
};
struct plugin_s2d_info {
struct adv_tracer_plugin *s2d_dev;
struct device *dev;
unsigned int enable;
unsigned int burnin_ctrl;
int sel_scanmode;
int dbgsel_sw;
bool arraydump_done;
int blk_count;
const char **blk_names;
};
#define DONE_ARRYDUMP 0xADADADAD
static struct plugin_s2d_info plugin_s2d;
int adv_tracer_s2d_scandump(void)
{
if (!plugin_s2d.burnin_ctrl || plugin_s2d.sel_scanmode < 0 ||
plugin_s2d.dbgsel_sw < 0) {
dev_err(plugin_s2d.dev, "pmu offset no data\n");
return -1;
}
exynos_pmu_update(plugin_s2d.burnin_ctrl,
BIT(plugin_s2d.sel_scanmode),
BIT(plugin_s2d.sel_scanmode));
dev_info(plugin_s2d.dev, "enter scandump mode!\n");
exynos_pmu_update(plugin_s2d.burnin_ctrl,
BIT(plugin_s2d.dbgsel_sw), BIT(plugin_s2d.dbgsel_sw));
return 0;
}
int adv_tracer_s2d_arraydump(void)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
u32 cpu_mask;
bitmap_to_arr32(&cpu_mask, cpumask_bits(cpu_possible_mask), 32);
if (!plugin_s2d.s2d_dev)
return -ENODEV;
if (plugin_s2d.arraydump_done == DONE_ARRYDUMP) {
dev_info(plugin_s2d.dev, "Arraydump already done(0x%x)\n",
cpu_mask);
return -1;
}
plugin_s2d.arraydump_done = DONE_ARRYDUMP;
dev_info(plugin_s2d.dev, "Start Arraydump (0x%x)\n", cpu_mask);
cmd.cmd_raw.cmd = EAT_IPC_CMD_ARRAYDUMP;
cmd.cmd_raw.id = ARR_IPC_CMD_ID_KERNEL_ARRAYDUMP;
cmd.buffer[1] = dbg_snapshot_get_item_paddr(DSS_ITEM_ARRDUMP_PANIC);
cmd.buffer[2] = cpu_mask;
ret = adv_tracer_ipc_send_data_polling_timeout(EAT_FRM_CHANNEL,
&cmd, EAT_IPC_TIMEOUT * 100);
if (ret < 0)
goto end;
dev_info(plugin_s2d.dev, "Finish Arraydump (0x%x)\n", cmd.buffer[1]);
end:
return ret;
}
static int adv_tracer_s2d_get_enable(void)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_GET_ENABLE;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "ipc can't get enable\n");
return ret;
}
plugin_s2d.enable = cmd.buffer[1];
return plugin_s2d.enable;
}
static int adv_tracer_s2d_set_enable(int en)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_SET_ENABLE;
cmd.buffer[1] = en;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "ipc can't enable setting\n");
return ret;
}
plugin_s2d.enable = en;
return 0;
}
static int adv_tracer_s2d_get_all_blk(unsigned long *p_blocks)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_GET_ALL_BLK;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "cannot get blk list\n");
return ret;
}
*p_blocks = (unsigned long)cmd.buffer[2] << 32 | cmd.buffer[1];
return 0;
}
int adv_tracer_s2d_get_blk_by_idx(unsigned int index)
{
unsigned long blocks;
int ret;
if (index >= plugin_s2d.blk_count)
return -EINVAL;
ret = adv_tracer_s2d_get_all_blk(&blocks);
if (ret)
return ret;
return !!(blocks & BIT(index));
}
int adv_tracer_s2d_get_blk_by_name(const char *name)
{
unsigned int i;
for (i = 0; i < plugin_s2d.blk_count; i++) {
if (!strcmp(name, plugin_s2d.blk_names[i]))
return adv_tracer_s2d_get_blk_by_idx(i);
}
return -EINVAL;
}
int adv_tracer_s2d_set_blk_by_idx(bool enabled, unsigned int index)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
if (index >= plugin_s2d.blk_count)
return -EINVAL;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_SET_BLK;
cmd.buffer[1] = enabled;
cmd.buffer[2] = index;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "cannot %sable %s blk\n",
enabled ? "en" : "dis",
plugin_s2d.blk_names[index]);
return ret;
}
if (cmd.cmd_raw.ret_err) {
dev_err(plugin_s2d.dev, "%sabling %s blk rejected\n",
enabled ? "en" : "dis",
plugin_s2d.blk_names[index]);
return -EPERM;
}
return 0;
}
int adv_tracer_s2d_set_blk_by_name(bool enabled, const char *name)
{
unsigned int i;
for (i = 0; i < plugin_s2d.blk_count; i++) {
if (!strcmp(name, plugin_s2d.blk_names[i]))
return adv_tracer_s2d_set_blk_by_idx(enabled, i);
}
return -ENOENT;
}
int adv_tracer_s2d_set_all_blk(bool en)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_SET_ALL_BLK;
cmd.buffer[1] = en;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "cannot set all blk\n");
return ret;
}
return 0;
}
static int adv_tracer_s2d_set_disable_gpr(int en)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_SET_DISABLE_GPR;
cmd.buffer[1] = en;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "ipc can't set disable_gpr (rc = %d)\n", ret);
return ret;
}
return 0;
}
static int adv_tracer_s2d_get_disable_gpr(void)
{
struct adv_tracer_ipc_cmd cmd = { 0 };
int ret = 0;
cmd.cmd_raw.cmd = eS2D_IPC_CMD_GET_DISABLE_GPR;
ret = adv_tracer_ipc_send_data(plugin_s2d.s2d_dev->id, &cmd);
if (ret < 0) {
dev_err(plugin_s2d.dev, "ipc can't get disable_gpr (rc = %d)\n", ret);
return ret;
}
return cmd.buffer[1];
}
static void adv_tracer_s2d_handler(struct adv_tracer_ipc_cmd *cmd,
unsigned int len)
{
}
static ssize_t s2d_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned int val;
int ret;
ret = kstrtouint(buf, 10, &val);
if (ret)
return ret;
adv_tracer_s2d_set_enable(!!val);
return size;
}
static ssize_t s2d_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%sable\n",
plugin_s2d.enable ? "en" : "dis");
}
static DEVICE_ATTR_RW(s2d_enable);
static ssize_t enable_block_by_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
char *name;
int ret = -EINVAL;
name = devm_kzalloc(dev, size + 1, GFP_KERNEL);
if (!name)
return -ENOMEM;
if (sscanf(buf, "%s", name) == 1)
ret = adv_tracer_s2d_set_blk_by_name(true, name);
devm_kfree(dev, name);
return ret ? ret : size;
}
static DEVICE_ATTR_WO(enable_block_by_name);
static ssize_t disable_block_by_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
char *name;
int ret = -EINVAL;
name = devm_kzalloc(dev, size + 1, GFP_KERNEL);
if (!name)
return -ENOMEM;
if (sscanf(buf, "%s", name) == 1)
ret = adv_tracer_s2d_set_blk_by_name(false, name);
devm_kfree(dev, name);
return ret ? ret : size;
}
static DEVICE_ATTR_WO(disable_block_by_name);
static ssize_t enable_block_by_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int ret = 0;
unsigned int index;
ret = kstrtouint(buf, 10, &index);
if (ret < 0)
return ret;
ret = adv_tracer_s2d_set_blk_by_idx(true, index);
return ret ? ret : size;
}
static DEVICE_ATTR_WO(enable_block_by_index);
static ssize_t disable_block_by_index_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int ret = 0;
unsigned int index;
ret = kstrtouint(buf, 10, &index);
if (ret < 0)
return ret;
ret = adv_tracer_s2d_set_blk_by_idx(false, index);
return ret ? ret : size;
}
static DEVICE_ATTR_WO(disable_block_by_index);
static ssize_t switch_all_block_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int ret = 0;
unsigned int all;
ret = kstrtouint(buf, 10, &all);
if (ret < 0)
return ret;
ret = adv_tracer_s2d_set_all_blk(!!all);
return ret ? ret : size;
}
static DEVICE_ATTR_WO(switch_all_block);
static ssize_t print_all_block_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int i, sz = 0;
unsigned long blocks;
int ret;
ret = adv_tracer_s2d_get_all_blk(&blocks);
if (ret)
return ret;
for (i = 0; i < plugin_s2d.blk_count; i++) {
sz += scnprintf(buf + sz, PAGE_SIZE - sz, "[%02u : %3s] %s\n",
i, blocks & BIT(i) ? "on" : "off",
plugin_s2d.blk_names[i]);
}
sz += scnprintf(buf + sz, PAGE_SIZE - sz, "\n");
return sz;
}
static DEVICE_ATTR_RO(print_all_block);
static ssize_t disable_gpr_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned int val;
int ret;
ret = kstrtouint(buf, 10, &val);
if (ret)
return ret;
ret = adv_tracer_s2d_set_disable_gpr(!!val);
if (ret)
return ret;
return size;
}
static ssize_t disable_gpr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = adv_tracer_s2d_get_disable_gpr();
if (ret < 0)
return ret;
return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "Disabled" : "Not disabled");
}
static DEVICE_ATTR_RW(disable_gpr);
static struct attribute *adv_tracer_s2d_sysfs_attrs[] = {
&dev_attr_s2d_enable.attr,
&dev_attr_enable_block_by_name.attr,
&dev_attr_enable_block_by_index.attr,
&dev_attr_disable_block_by_name.attr,
&dev_attr_disable_block_by_index.attr,
&dev_attr_switch_all_block.attr,
&dev_attr_print_all_block.attr,
&dev_attr_disable_gpr.attr,
NULL,
};
ATTRIBUTE_GROUPS(adv_tracer_s2d_sysfs);
static int adv_tracer_s2d_dt_init(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
if (of_property_read_u32(node, "pmu-burnin-ctrl",
&plugin_s2d.burnin_ctrl))
dev_err(&pdev->dev, "pmu-burnin-ctrl is no data\n");
if (of_property_read_u32(node, "sel-scanmode-bit",
&plugin_s2d.sel_scanmode))
plugin_s2d.sel_scanmode = -1;
if (of_property_read_u32(node, "dbgsel-sw-bit",
&plugin_s2d.dbgsel_sw))
plugin_s2d.dbgsel_sw = -1;
plugin_s2d.blk_count = of_property_count_strings(node, "blk-list");
if (plugin_s2d.blk_count <= 0 || plugin_s2d.blk_count > 64) {
dev_err(&pdev->dev, "Failed to get blk list.\n");
return -EINVAL;
}
plugin_s2d.blk_names = devm_kcalloc(&pdev->dev, plugin_s2d.blk_count,
sizeof(char *), GFP_KERNEL);
if (!plugin_s2d.blk_names)
return -ENOMEM;
of_property_read_string_array(node, "blk-list",
plugin_s2d.blk_names, plugin_s2d.blk_count);
return 0;
}
static int adv_tracer_s2d_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct adv_tracer_plugin *s2d = NULL;
int ret;
s2d = devm_kzalloc(&pdev->dev, sizeof(struct adv_tracer_plugin),
GFP_KERNEL);
if (!s2d) {
dev_err(&pdev->dev, "can not allocate mem for s2d\n");
ret = -ENOMEM;
goto err_s2d_info;
}
plugin_s2d.dev = &pdev->dev;
plugin_s2d.s2d_dev = s2d;
ret = adv_tracer_ipc_request_channel(node, adv_tracer_s2d_handler,
&s2d->id, &s2d->len);
if (ret < 0) {
dev_err(&pdev->dev, "s2d ipc request fail(%d)\n", ret);
goto err_sysfs_probe;
}
ret = adv_tracer_s2d_get_enable();
if (ret < 0)
goto err_sysfs_probe;
dev_info(&pdev->dev, "S2D %sabled\n", plugin_s2d.enable ? "en" : "dis");
ret = adv_tracer_s2d_dt_init(pdev);
if (ret < 0)
goto err_sysfs_probe;
platform_set_drvdata(pdev, s2d);
ret = sysfs_create_groups(&pdev->dev.kobj, adv_tracer_s2d_sysfs_groups);
if (ret) {
dev_err(&pdev->dev, "fail to register sysfs.\n");
return ret;
}
dbg_snapshot_register_debug_ops(NULL,
adv_tracer_s2d_arraydump,
adv_tracer_s2d_scandump);
keydebug_register_s2d_ops(adv_tracer_s2d_get_enable, adv_tracer_s2d_set_enable);
dev_info(&pdev->dev, "%s successful.\n", __func__);
return 0;
err_sysfs_probe:
devm_kfree(&pdev->dev, s2d);
err_s2d_info:
return ret;
}
static int adv_tracer_s2d_remove(struct platform_device *pdev)
{
struct adv_tracer_plugin *s2d = platform_get_drvdata(pdev);
adv_tracer_ipc_release_channel(s2d->id);
return 0;
}
static const struct of_device_id adv_tracer_s2d_match[] = {
{ .compatible = "google,exynos-adv-tracer-s2d", },
{},
};
MODULE_DEVICE_TABLE(of, adv_tracer_s2d_match);
static struct platform_driver adv_tracer_s2d_driver = {
.probe = adv_tracer_s2d_probe,
.remove = adv_tracer_s2d_remove,
.driver = {
.name = "exynos-adv-tracer-s2d",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(adv_tracer_s2d_match),
},
};
module_platform_driver(adv_tracer_s2d_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EXYNOS Advanced Tracer for S2D Driver");