kernel_samsung_a53x/drivers/soc/samsung/debug/exynos-adv-tracer.c
2024-06-15 16:02:09 -03:00

541 lines
13 KiB
C
Executable file

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/ktime.h>
#include <soc/samsung/debug-snapshot.h>
#include <soc/samsung/exynos-adv-tracer-ipc.h>
#include <soc/samsung/exynos-pmu-if.h>
static struct adv_tracer_info *exynos_ati;
static struct adv_tracer_ipc_main *adv_tracer_ipc;
static void adv_tracer_ipc_read_buffer(void *dest, const void *src,
unsigned int len)
{
const unsigned int *sp = src;
unsigned int *dp = dest;
int i;
for (i = 0; i < len; i++)
*dp++ = readl(sp++);
}
static void adv_tracer_ipc_write_buffer(void *dest, const void *src,
unsigned int len)
{
const unsigned int *sp = src;
unsigned int *dp = dest;
int i;
for (i = 0; i < len; i++)
writel(*sp++, dp++);
}
static void adv_tracer_ipc_interrupt_clear(int id)
{
__raw_writel((1 << INTR_FLAG_OFFSET) << id,
adv_tracer_ipc->mailbox_base + AP_INTCR);
}
static irqreturn_t adv_tracer_ipc_irq_handler(int irq, void *data)
{
struct adv_tracer_ipc_main *ipc = data;
struct adv_tracer_ipc_cmd *cmd;
struct adv_tracer_ipc_ch *channel;
unsigned int status;
int i;
/* ADV_TRACER IPC INTERRUPT STATUS REGISTER */
status = __raw_readl(adv_tracer_ipc->mailbox_base + AP_INTSR);
status = status >> INTR_FLAG_OFFSET;
for (i = 0; i < adv_tracer_ipc->num_channels; i++) {
channel = &adv_tracer_ipc->channel[i];
if (status & (0x1 << channel->id)) {
cmd = channel->cmd;
adv_tracer_ipc_read_buffer(cmd->buffer,
channel->buff_regs, channel->len);
/* ADV_TRACER IPC INTERRUPT PENDING CLEAR */
adv_tracer_ipc_interrupt_clear(channel->id);
}
}
ipc->mailbox_status = status;
return IRQ_WAKE_THREAD;
}
static irqreturn_t adv_tracer_ipc_irq_handler_thread(int irq, void *data)
{
struct adv_tracer_ipc_main *ipc = data;
struct adv_tracer_ipc_cmd *cmd;
struct adv_tracer_ipc_ch *channel;
unsigned int status;
int i;
status = ipc->mailbox_status;
for (i = 0; i < adv_tracer_ipc->num_channels; i++) {
channel = &adv_tracer_ipc->channel[i];
if (status & (1 << i)) {
cmd = channel->cmd;
if (cmd->cmd_raw.response)
complete(&channel->wait);
if (cmd->cmd_raw.one_way || cmd->cmd_raw.response) {
/* HANDLE to Plugin handler */
if (channel->ipc_callback)
channel->ipc_callback(cmd,
channel->len);
}
}
}
return IRQ_HANDLED;
}
static void adv_tracer_interrupt_gen(unsigned int id)
{
writel(1 << id, adv_tracer_ipc->mailbox_base + INTGR_AP_TO_DBGC);
}
int adv_tracer_ipc_send_data(unsigned int id, struct adv_tracer_ipc_cmd *cmd)
{
struct adv_tracer_ipc_ch *channel;
int ret;
if (id >= EAT_MAX_CHANNEL && IS_ERR(cmd))
return -EIO;
channel = &adv_tracer_ipc->channel[id];
cmd->cmd_raw.manual_polling = 0;
cmd->cmd_raw.one_way = 0;
cmd->cmd_raw.response = 0;
spin_lock(&channel->ch_lock);
memcpy(channel->cmd, cmd, sizeof(unsigned int) * channel->len);
adv_tracer_ipc_write_buffer(channel->buff_regs, channel->cmd,
channel->len);
adv_tracer_interrupt_gen(channel->id);
spin_unlock(&channel->ch_lock);
ret = wait_for_completion_interruptible_timeout(&channel->wait,
msecs_to_jiffies(1000));
if (!ret) {
dev_err(exynos_ati->dev, "%d channel timeout error\n", id);
return -EBUSY;
}
memcpy(cmd, channel->cmd, sizeof(unsigned int) * channel->len);
return 0;
}
EXPORT_SYMBOL(adv_tracer_ipc_send_data);
int adv_tracer_ipc_send_data_polling_timeout(unsigned int id,
struct adv_tracer_ipc_cmd *cmd, unsigned long tmout_ns)
{
struct adv_tracer_ipc_ch *channel = NULL;
u64 timeout, now;
struct adv_tracer_ipc_cmd _cmd;
if (id >= EAT_MAX_CHANNEL && IS_ERR(cmd))
return -EIO;
channel = &adv_tracer_ipc->channel[id];
cmd->cmd_raw.manual_polling = 1;
cmd->cmd_raw.one_way = 0;
cmd->cmd_raw.response = 0;
spin_lock(&channel->ch_lock);
memcpy(channel->cmd, cmd, sizeof(unsigned int) * channel->len);
adv_tracer_ipc_write_buffer(channel->buff_regs, channel->cmd,
channel->len);
adv_tracer_interrupt_gen(channel->id);
spin_unlock(&channel->ch_lock);
timeout = local_clock() + tmout_ns;
now = local_clock();
do {
now = local_clock();
adv_tracer_ipc_read_buffer(&_cmd.buffer[0],
channel->buff_regs, 1);
} while (!_cmd.cmd_raw.response && (timeout > now));
if (!_cmd.cmd_raw.response) {
dev_err(exynos_ati->dev, "%d channel timeout error\n", id);
return -EBUSY;
}
adv_tracer_ipc_read_buffer(cmd->buffer, channel->buff_regs, channel->len);
return 0;
}
EXPORT_SYMBOL(adv_tracer_ipc_send_data_polling_timeout);
int adv_tracer_ipc_send_data_polling(unsigned int id,
struct adv_tracer_ipc_cmd *cmd)
{
return adv_tracer_ipc_send_data_polling_timeout(id, cmd,
EAT_IPC_TIMEOUT);
}
EXPORT_SYMBOL(adv_tracer_ipc_send_data_polling);
int adv_tracer_ipc_send_data_async(unsigned int id,
struct adv_tracer_ipc_cmd *cmd)
{
struct adv_tracer_ipc_ch *channel = NULL;
if (id >= EAT_MAX_CHANNEL && IS_ERR(cmd))
return -EIO;
channel = &adv_tracer_ipc->channel[id];
cmd->cmd_raw.manual_polling = 0;
cmd->cmd_raw.one_way = 1;
cmd->cmd_raw.response = 0;
spin_lock(&channel->ch_lock);
memcpy(channel->cmd, cmd, sizeof(unsigned int) * channel->len);
adv_tracer_ipc_write_buffer(channel->buff_regs, channel->cmd,
channel->len);
adv_tracer_interrupt_gen(channel->id);
spin_unlock(&channel->ch_lock);
return 0;
}
EXPORT_SYMBOL(adv_tracer_ipc_send_data_async);
struct adv_tracer_ipc_cmd *adv_tracer_ipc_get_channel_cmd(unsigned int id)
{
struct adv_tracer_ipc_ch *channel = NULL;
struct adv_tracer_ipc_cmd *cmd = NULL;
if (IS_ERR(adv_tracer_ipc))
goto out;
channel = &adv_tracer_ipc->channel[id];
if (IS_ERR(channel))
goto out;
if (!channel->used)
goto out;
cmd = channel->cmd;
out:
return cmd;
}
EXPORT_SYMBOL(adv_tracer_ipc_get_channel_cmd);
struct adv_tracer_ipc_ch *adv_tracer_ipc_get_channel(unsigned int id)
{
struct adv_tracer_ipc_ch *ipc_channel = NULL;
if (IS_ERR(adv_tracer_ipc))
goto out;
ipc_channel = &adv_tracer_ipc->channel[id];
if (IS_ERR(ipc_channel)) {
dev_err(exynos_ati->dev, "%d channel is not allocated\n", id);
ipc_channel = NULL;
}
out:
return ipc_channel;
}
EXPORT_SYMBOL(adv_tracer_ipc_get_channel);
static int adv_tracer_ipc_channel_init(unsigned int id, unsigned int offset,
unsigned int len, ipc_callback handler,
char *name)
{
struct adv_tracer_ipc_ch *channel = &adv_tracer_ipc->channel[id];
if (channel->used) {
dev_err(exynos_ati->dev, "%d channel is already reserved(%s)\n",
id, channel->id_name);
return -1;
}
channel->id = id;
channel->offset = offset;
channel->len = len;
strncpy(channel->id_name, name, sizeof(unsigned int) - 1);
channel->id_name[3] = '\0';
/* channel->buff_regs -> shared buffer by owns */
channel->buff_regs =
(void __iomem *)(adv_tracer_ipc->mailbox_base + offset);
channel->cmd = devm_kzalloc(adv_tracer_ipc->dev,
sizeof(struct adv_tracer_ipc_cmd), GFP_KERNEL);
channel->ipc_callback = handler;
if (IS_ERR(channel->cmd))
return PTR_ERR(channel->cmd);
channel->used = true;
return 0;
}
int adv_tracer_ipc_release_channel(unsigned int id)
{
struct adv_tracer_ipc_cmd *cmd;
int ret;
if (!adv_tracer_ipc->channel[id].used) {
dev_err(exynos_ati->dev, "%d channel is unused\n", id);
return -1;
}
cmd = adv_tracer_ipc_get_channel_cmd(EAT_FRM_CHANNEL);
if (!cmd) {
dev_err(exynos_ati->dev, "%d channel is failed to release\n", id);
return -EIO;
}
cmd->cmd_raw.cmd = EAT_IPC_CMD_CH_RELEASE;
cmd->buffer[1] = id;
ret = adv_tracer_ipc_send_data(EAT_FRM_CHANNEL, cmd);
if (ret < 0) {
dev_err(exynos_ati->dev, "%d channel is failed to release\n", id);
return ret;
}
return 0;
}
EXPORT_SYMBOL(adv_tracer_ipc_release_channel);
void adv_tracer_ipc_release_channel_by_name(const char *name)
{
struct adv_tracer_ipc_ch *ipc_ch = adv_tracer_ipc->channel;
int i;
for (i = 0; i < adv_tracer_ipc->num_channels; i++) {
if (ipc_ch[i].used) {
if (!strncmp(name, ipc_ch[i].id_name, strlen(name))) {
ipc_ch[i].used = 0;
break;
}
}
}
}
EXPORT_SYMBOL(adv_tracer_ipc_release_channel_by_name);
int adv_tracer_ipc_ramhold_ctrl(bool en, bool clear)
{
struct adv_tracer_ipc_cmd cmd;
int ret = 0;
cmd.cmd_raw.cmd = EAT_IPC_CMD_RAMHOLD_CTRL;
cmd.buffer[1] = en;
cmd.buffer[2] = clear;
ret = adv_tracer_ipc_send_data_polling_timeout(EAT_FRM_CHANNEL,
&cmd, EAT_IPC_TIMEOUT);
if (ret < 0)
goto end;
if (en != cmd.buffer[1])
return -EINVAL;
end:
return ret;
}
EXPORT_SYMBOL_GPL(adv_tracer_ipc_ramhold_ctrl);
int adv_tracer_ipc_request_channel(struct device_node *np,
ipc_callback handler,
unsigned int *id,
unsigned int *len)
{
const __be32 *prop;
const char *plugin_name;
unsigned int plugin_len, offset;
struct adv_tracer_ipc_cmd cmd;
int ret;
if (!np)
return -ENODEV;
prop = of_get_property(np, "plugin-len", &plugin_len);
if (!prop)
return -ENOENT;
plugin_len = be32_to_cpup(prop);
plugin_name = of_get_property(np, "plugin-name", NULL);
if (!plugin_name)
return -ENOENT;
cmd.cmd_raw.cmd = EAT_IPC_CMD_CH_INIT;
cmd.buffer[1] = plugin_len;
memcpy(&cmd.buffer[2], plugin_name, sizeof(unsigned int));
ret = adv_tracer_ipc_send_data(EAT_FRM_CHANNEL, &cmd);
if (ret) {
dev_err(exynos_ati->dev, "%d channel is failed to request\n",
EAT_FRM_CHANNEL);
return -ENODEV;
}
*len = cmd.buffer[1];
offset = SR(cmd.buffer[2]);
*id = cmd.buffer[3];
ret = adv_tracer_ipc_channel_init(*id, offset, *len, handler,
(char *)plugin_name);
if (ret) {
dev_err(exynos_ati->dev,
"%d channel is failed to init for %s(%d)\n",
*id, plugin_name, ret);
return -ENODEV;
}
return 0;
}
EXPORT_SYMBOL(adv_tracer_ipc_request_channel);
static void adv_tracer_ipc_channel_clear(void)
{
struct adv_tracer_ipc_ch *channel;
channel = &adv_tracer_ipc->channel[EAT_FRM_CHANNEL];
channel->cmd->cmd_raw.cmd = EAT_IPC_CMD_CH_CLEAR;
channel->cmd->cmd_raw.one_way = 1;
adv_tracer_ipc_write_buffer(channel->buff_regs, channel->cmd, 1);
adv_tracer_interrupt_gen(EAT_FRM_CHANNEL);
}
static void adv_tracer_ipc_callback(struct adv_tracer_ipc_cmd *cmd,
unsigned int len)
{
switch(cmd->cmd_raw.cmd) {
case EAT_IPC_CMD_BOOT_DBGC:
dev_info(adv_tracer_ipc->dev, "DBGC Boot!(cnt:%d)\n", cmd->buffer[1]);
adv_tracer_ipc->exception_cnt--;
break;
case EAT_IPC_CMD_EXCEPTION_DBGC:
dev_err(adv_tracer_ipc->dev,
"DBGC occurred exception! (SP: 0x%x, LR:0x%x, PC:0x%x)\n",
cmd->buffer[1], cmd->buffer[2], cmd->buffer[3]);
adv_tracer_ipc->exception_cnt++;
if (adv_tracer_ipc->exception_cnt >= 3)
dbg_snapshot_expire_watchdog();
break;
default:
break;
}
}
static int adv_tracer_ipc_channel_probe(void)
{
int i, ret;
adv_tracer_ipc->num_channels = EAT_MAX_CHANNEL;
adv_tracer_ipc->channel = devm_kcalloc(adv_tracer_ipc->dev,
adv_tracer_ipc->num_channels,
sizeof(struct adv_tracer_ipc_ch),
GFP_KERNEL);
if (IS_ERR(adv_tracer_ipc->channel))
return PTR_ERR(adv_tracer_ipc->channel);
for (i = 0; i < adv_tracer_ipc->num_channels; i++) {
init_completion(&adv_tracer_ipc->channel[i].wait);
spin_lock_init(&adv_tracer_ipc->channel[i].ch_lock);
}
ret = adv_tracer_ipc_channel_init(EAT_FRM_CHANNEL, SR(16), 4,
adv_tracer_ipc_callback, FRAMEWORK_NAME);
if (ret) {
dev_err(exynos_ati->dev, "failed to register framework channel\n", ret);
return ret;
}
adv_tracer_ipc_channel_clear();
return 0;
}
static int adv_tracer_ipc_init(struct platform_device *pdev)
{
int ret = 0;
adv_tracer_ipc = devm_kzalloc(&pdev->dev,
sizeof(struct adv_tracer_ipc_main), GFP_KERNEL);
if (IS_ERR(adv_tracer_ipc))
return PTR_ERR(adv_tracer_ipc);
/* Mailbox interrupt, AP -- Debug Core */
adv_tracer_ipc->irq = platform_get_irq(pdev, 0);
/* Mailbox base register */
adv_tracer_ipc->mailbox_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(adv_tracer_ipc->mailbox_base))
return PTR_ERR(adv_tracer_ipc->mailbox_base);
adv_tracer_ipc_interrupt_clear(EAT_FRM_CHANNEL);
ret = devm_request_threaded_irq(&pdev->dev,
adv_tracer_ipc->irq, adv_tracer_ipc_irq_handler,
adv_tracer_ipc_irq_handler_thread,
IRQF_ONESHOT,
dev_name(&pdev->dev), adv_tracer_ipc);
if (ret) {
dev_err(&pdev->dev, "failed to register interrupt: %d\n", ret);
return ret;
}
adv_tracer_ipc->dev = &pdev->dev;
ret = adv_tracer_ipc_channel_probe();
if (ret) {
dev_err(&pdev->dev, "failed to register channel: %d\n", ret);
return ret;
}
dev_info(&pdev->dev, "%s successful.\n", __func__);
return ret;
}
static int adv_tracer_probe(struct platform_device *pdev)
{
struct adv_tracer_info *adv_tracer;
int ret = 0;
adv_tracer = devm_kzalloc(&pdev->dev,
sizeof(struct adv_tracer_info), GFP_KERNEL);
if (IS_ERR(adv_tracer))
return PTR_ERR(adv_tracer);
adv_tracer->dev = &pdev->dev;
if (adv_tracer_ipc_init(pdev))
goto out;
exynos_ati = adv_tracer;
dev_info(&pdev->dev, "%s successful.\n", __func__);
out:
return ret;
}
static const struct of_device_id adv_tracer_match[] = {
{ .compatible = "samsung,exynos-adv-tracer" },
{},
};
static struct platform_driver exynos_adv_tracer_driver = {
.probe = adv_tracer_probe,
.driver = {
.name = "exynos-adv-tracer",
.owner = THIS_MODULE,
.of_match_table = adv_tracer_match,
},
};
module_platform_driver(exynos_adv_tracer_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Exynos Advanced Tracer");