1461 lines
40 KiB
C
Executable file
1461 lines
40 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox Debug driver
|
|
*
|
|
* Copyright (c) 2016 Samsung Electronics Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/mm_types.h>
|
|
#include <asm/cacheflush.h>
|
|
|
|
#include "abox_util.h"
|
|
#include "abox_proc.h"
|
|
#include "abox_gic.h"
|
|
#include "abox_core.h"
|
|
#include "abox_oem.h"
|
|
#include "abox_dbg.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#define ABOX_DBG_DUMP_MAGIC_SRAM 0x3935303030504D44ull /* DMP00059 */
|
|
#define ABOX_DBG_DUMP_MAGIC_DRAM 0x3231303038504D44ull /* DMP80012 */
|
|
#define ABOX_DBG_DUMP_MAGIC_LOG 0x3142303038504D44ull /* DMP800B1 */
|
|
#define ABOX_DBG_DUMP_MAGIC_SFR 0x5246533030504D44ull /* DMP00SFR */
|
|
#define ABOX_DBG_DUMP_MAGIC_ATUNE 0x5554413030504D44ull /* DMP00ATU */
|
|
#define ABOX_DBG_DUMP_LIMIT_NS (5 * NSEC_PER_SEC)
|
|
|
|
static struct mutex lock;
|
|
|
|
void abox_dbg_print_gpr_from_addr(struct device *dev, struct abox_data *data,
|
|
unsigned int *addr)
|
|
{
|
|
abox_core_print_gpr_dump(addr);
|
|
}
|
|
|
|
void abox_dbg_print_gpr(struct device *dev, struct abox_data *data)
|
|
{
|
|
abox_core_print_gpr();
|
|
}
|
|
|
|
struct abox_dbg_dump_sram {
|
|
unsigned long long magic;
|
|
char dump[SRAM_FIRMWARE_SIZE];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_dram {
|
|
unsigned long long magic;
|
|
char dump[DRAM_FIRMWARE_SIZE];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_log {
|
|
unsigned long long magic;
|
|
char dump[ABOX_LOG_SIZE];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_sfr {
|
|
unsigned long long magic;
|
|
u32 dump[SZ_64K / sizeof(u32)];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_dram dram;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[SZ_128];
|
|
struct abox_dbg_dump_sfr atune;
|
|
long long time;
|
|
char reason[SZ_32];
|
|
unsigned int previous_gpr;
|
|
unsigned int previous_mem;
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_min {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_log log;
|
|
struct abox_dbg_dump_log log_01;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[SZ_128];
|
|
struct abox_dbg_dump_sfr atune;
|
|
long long time;
|
|
char reason[SZ_32];
|
|
unsigned int previous_gpr;
|
|
unsigned int previous_mem;
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_info {
|
|
struct abox_proc_bin sram;
|
|
struct abox_proc_bin dram;
|
|
struct abox_proc_bin log;
|
|
struct abox_proc_bin log_01;
|
|
struct abox_proc_bin sfr;
|
|
struct abox_proc_bin gicd;
|
|
struct abox_proc_bin gpr;
|
|
struct abox_proc_bin reason;
|
|
};
|
|
|
|
static struct abox_dbg_dump (*p_abox_dbg_dump)[ABOX_DBG_DUMP_COUNT];
|
|
static struct abox_dbg_dump_min (*p_abox_dbg_dump_min)[ABOX_DBG_DUMP_COUNT];
|
|
static struct abox_dbg_dump_info abox_dbg_dump_info[ABOX_DBG_DUMP_COUNT];
|
|
|
|
/* revisited free_reserved_area() of /mm/page_alloc.c */
|
|
static unsigned long __free_reserved_area(phys_addr_t start, phys_addr_t end, const char *s)
|
|
{
|
|
phys_addr_t pos;
|
|
unsigned long pages = 0;
|
|
|
|
start = PAGE_ALIGN(start);
|
|
end &= PAGE_MASK;
|
|
for (pos = start; pos < end; pos += PAGE_SIZE, pages++)
|
|
free_reserved_page(phys_to_page(pos));
|
|
|
|
if (pages && s)
|
|
pr_info("Freeing %s memory: %ldK\n", s, pages << (PAGE_SHIFT - 10));
|
|
|
|
return pages;
|
|
}
|
|
|
|
static void abox_dbg_resize_rmem(struct device *dev, struct reserved_mem *rmem,
|
|
size_t new_size, const char *tag)
|
|
{
|
|
size_t old_size = rmem->size;
|
|
|
|
if (old_size < new_size) {
|
|
abox_warn(dev, "%s: new size %#zx is bigger than reserved size %#zx\n",
|
|
tag, new_size, old_size);
|
|
return;
|
|
}
|
|
|
|
rmem->size = new_size;
|
|
__free_reserved_area(rmem->base + new_size, rmem->base + old_size, tag);
|
|
abox_info(dev, "%s: %s new size %#lx\n", __func__, tag, rmem->size);
|
|
}
|
|
|
|
static struct reserved_mem *abox_dbg_slog;
|
|
|
|
static int __init abox_dbg_slog_setup(struct reserved_mem *rmem)
|
|
{
|
|
pr_info("%s: size=%pa\n", __func__, &rmem->size);
|
|
abox_dbg_slog = rmem;
|
|
return 0;
|
|
}
|
|
|
|
RESERVEDMEM_OF_DECLARE(abox_dbg_slog, "exynos,abox_slog", abox_dbg_slog_setup);
|
|
|
|
static void abox_dbg_slog_init(struct abox_data *data)
|
|
{
|
|
struct device *dev_abox = data->dev;
|
|
|
|
abox_info(dev_abox, "%s: size=%pa\n", __func__, &abox_dbg_slog->size);
|
|
|
|
data->slog_phys = abox_dbg_slog->base;
|
|
data->slog_size = abox_dbg_slog->size;
|
|
data->slog_base = rmem_vmap(abox_dbg_slog);
|
|
abox_iommu_map(dev_abox, IOVA_SILENT_LOG, data->slog_phys,
|
|
data->slog_size, data->slog_base);
|
|
}
|
|
|
|
static struct reserved_mem *abox_dbg_rmem;
|
|
|
|
static int __init abox_dbg_rmem_setup(struct reserved_mem *rmem)
|
|
{
|
|
pr_info("%s: size=%pa\n", __func__, &rmem->size);
|
|
abox_dbg_rmem = rmem;
|
|
return 0;
|
|
}
|
|
|
|
RESERVEDMEM_OF_DECLARE(abox_dbg_rmem, "exynos,abox_dbg", abox_dbg_rmem_setup);
|
|
|
|
static bool abox_dbg_dump_valid(int idx)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (idx >= ABOX_DBG_DUMP_COUNT)
|
|
return false;
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[idx];
|
|
|
|
ret = (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR);
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[idx];
|
|
|
|
ret = (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void abox_dbg_clear_valid(int idx)
|
|
{
|
|
if (idx >= ABOX_DBG_DUMP_COUNT)
|
|
return;
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[idx];
|
|
|
|
p_dump->sfr.magic = 0;
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[idx];
|
|
|
|
p_dump->sfr.magic = 0;
|
|
}
|
|
}
|
|
|
|
static ssize_t abox_dbg_read_valid(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int idx = (int)(long)abox_proc_data(file);
|
|
bool valid = abox_dbg_dump_valid(idx);
|
|
char buf_val[4]; /* enough to store a bool and "\n\0" */
|
|
|
|
if (valid)
|
|
buf_val[0] = 'Y';
|
|
else
|
|
buf_val[0] = 'N';
|
|
buf_val[1] = '\n';
|
|
buf_val[2] = 0x00;
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf_val, 2);
|
|
}
|
|
|
|
static const struct proc_ops abox_dbg_fops_valid = {
|
|
.proc_open = simple_open,
|
|
.proc_read = abox_dbg_read_valid,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static ssize_t abox_dbg_read_clear(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int idx = (int)(long)abox_proc_data(file);
|
|
|
|
abox_dbg_clear_valid(idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t abox_dbg_write_clear(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int idx = (int)(long)abox_proc_data(file);
|
|
|
|
abox_dbg_clear_valid(idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct proc_ops abox_dbg_fops_clear = {
|
|
.proc_open = simple_open,
|
|
.proc_read = abox_dbg_read_clear,
|
|
.proc_write = abox_dbg_write_clear,
|
|
.proc_lseek = no_llseek,
|
|
};
|
|
|
|
static void dump_gpr_from_addr_full(struct device *dev, unsigned int *addr,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr_dump(p_dump->gpr, addr);
|
|
}
|
|
|
|
static void dump_gpr_from_addr_half(struct device *dev, unsigned int *addr,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0];
|
|
|
|
if (src == ABOX_DBG_DUMP_KERNEL) {
|
|
if (abox_dbg_dump_valid(0) && p_dump->previous_gpr == 0) {
|
|
abox_info(dev, "%s(%d): skipped\n", __func__, src);
|
|
return;
|
|
}
|
|
}
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr_dump(p_dump->gpr, addr);
|
|
}
|
|
|
|
static void dump_gpr_from_addr_min(struct device *dev, unsigned int *addr,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr_dump(p_dump->gpr, addr);
|
|
}
|
|
|
|
static void dump_gpr_full(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr(p_dump->gpr);
|
|
}
|
|
|
|
static void dump_gpr_half(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0];
|
|
|
|
if (src == ABOX_DBG_DUMP_KERNEL) {
|
|
if (abox_dbg_dump_valid(0) && p_dump->previous_gpr == 0) {
|
|
abox_info(dev, "%s(%d): skipped\n", __func__, src);
|
|
return;
|
|
}
|
|
}
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr(p_dump->gpr);
|
|
}
|
|
|
|
static void dump_gpr_min(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_gpr = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
abox_core_dump_gpr(p_dump->gpr);
|
|
}
|
|
|
|
static void dump_mem_full(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_mem = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
memcpy_fromio(p_dump->sram.dump, data->sram_base,
|
|
SRAM_FIRMWARE_SIZE);
|
|
p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy(p_dump->dram.dump, data->dram_base, DRAM_FIRMWARE_SIZE);
|
|
p_dump->dram.magic = ABOX_DBG_DUMP_MAGIC_DRAM;
|
|
memcpy_fromio(p_dump->sfr.dump, data->sfr_base,
|
|
sizeof(p_dump->sfr.dump));
|
|
p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET,
|
|
sizeof(p_dump->atune.dump));
|
|
p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE;
|
|
abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0,
|
|
sizeof(p_dump->sfr_gic_gicd));
|
|
}
|
|
|
|
static void dump_mem_half(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[0];
|
|
|
|
if (src == ABOX_DBG_DUMP_KERNEL) {
|
|
if (abox_dbg_dump_valid(0) && p_dump->previous_mem == 0) {
|
|
abox_info(dev, "%s(%d): skipped\n", __func__, src);
|
|
return;
|
|
}
|
|
}
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_mem = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
memcpy_fromio(p_dump->sram.dump, data->sram_base,
|
|
SRAM_FIRMWARE_SIZE);
|
|
p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy(p_dump->dram.dump, data->dram_base, DRAM_FIRMWARE_SIZE);
|
|
p_dump->dram.magic = ABOX_DBG_DUMP_MAGIC_DRAM;
|
|
memcpy_fromio(p_dump->sfr.dump, data->sfr_base,
|
|
sizeof(p_dump->sfr.dump));
|
|
p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET,
|
|
sizeof(p_dump->atune.dump));
|
|
p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE;
|
|
abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0,
|
|
sizeof(p_dump->sfr_gic_gicd));
|
|
}
|
|
|
|
static void dump_mem_min(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time)
|
|
{
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = time;
|
|
p_dump->previous_mem = 0;
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
memcpy_fromio(p_dump->sram.dump, data->sram_base,
|
|
SRAM_FIRMWARE_SIZE);
|
|
p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
|
|
memcpy(p_dump->log.dump, data->dram_base + ABOX_LOG_OFFSET,
|
|
ABOX_LOG_SIZE);
|
|
p_dump->log.magic = ABOX_DBG_DUMP_MAGIC_LOG;
|
|
memcpy(p_dump->log_01.dump, data->dram_base + ABOX_LOG_MAJOR_OFFSET,
|
|
ABOX_LOG_SIZE);
|
|
p_dump->log_01.magic = ABOX_DBG_DUMP_MAGIC_LOG;
|
|
|
|
memcpy_fromio(p_dump->sfr.dump, data->sfr_base,
|
|
sizeof(p_dump->sfr.dump));
|
|
p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(p_dump->atune.dump, data->sfr_base + ATUNE_OFFSET,
|
|
sizeof(p_dump->atune.dump));
|
|
p_dump->atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE;
|
|
abox_gicd_dump(data->dev_gic, (char *)p_dump->sfr_gic_gicd, 0,
|
|
sizeof(p_dump->sfr_gic_gicd));
|
|
}
|
|
|
|
static int abox_dbg_dump_count;
|
|
static void (*p_dump_gpr_from_addr)(struct device *dev, unsigned int *addr,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time);
|
|
static void (*p_dump_gpr)(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time);
|
|
static void (*p_dump_mem)(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason,
|
|
unsigned long long time);
|
|
|
|
static int abox_dbg_dump_create_file(struct abox_data *data)
|
|
{
|
|
const char *dir_fmt = "snapshot_%d";
|
|
struct proc_dir_entry *dir;
|
|
struct abox_dbg_dump_info *info;
|
|
char *dir_name;
|
|
int i;
|
|
|
|
abox_dbg(data->dev, "%s\n", __func__);
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump;
|
|
|
|
for (i = 0; i < abox_dbg_dump_count; i++) {
|
|
dir_name = kasprintf(GFP_KERNEL, dir_fmt, i);
|
|
dir = abox_proc_mkdir(dir_name, NULL);
|
|
p_dump = &(*p_abox_dbg_dump)[i];
|
|
info = &abox_dbg_dump_info[i];
|
|
|
|
info->sram.data = p_dump->sram.dump;
|
|
info->sram.size = sizeof(p_dump->sram.dump);
|
|
abox_proc_create_bin("sram", 0440, dir, &info->sram);
|
|
|
|
info->dram.data = p_dump->dram.dump;
|
|
info->dram.size = sizeof(p_dump->dram.dump);
|
|
abox_proc_create_bin("dram", 0440, dir, &info->dram);
|
|
|
|
info->log.data = p_dump->dram.dump + ABOX_LOG_OFFSET;
|
|
info->log.size = ABOX_LOG_SIZE;
|
|
abox_proc_create_bin("log", 0444, dir, &info->log);
|
|
|
|
info->sfr.data = p_dump->sfr.dump;
|
|
info->sfr.size = sizeof(p_dump->sfr.dump);
|
|
abox_proc_create_bin("sfr", 0440, dir, &info->sfr);
|
|
|
|
info->gicd.data = p_dump->sfr_gic_gicd;
|
|
info->gicd.size = sizeof(p_dump->sfr_gic_gicd);
|
|
abox_proc_create_bin("gicd", 0440, dir, &info->gicd);
|
|
|
|
info->gpr.data = p_dump->gpr;
|
|
info->gpr.size = sizeof(p_dump->gpr);
|
|
abox_proc_create_bin("gpr", 0440, dir, &info->gpr);
|
|
|
|
abox_proc_create_u64("time", 0440, dir, &p_dump->time);
|
|
|
|
info->reason.data = p_dump->reason;
|
|
info->reason.size = sizeof(p_dump->reason);
|
|
abox_proc_create_bin("reason", 0440, dir,
|
|
&info->reason);
|
|
|
|
abox_proc_create_u32("previous", 0440, dir,
|
|
&p_dump->previous_mem);
|
|
|
|
abox_proc_create_file("valid", 0440, dir,
|
|
&abox_dbg_fops_valid,
|
|
(void *)(long)i, 0);
|
|
|
|
abox_proc_create_file("clear", 0440, dir,
|
|
&abox_dbg_fops_clear,
|
|
(void *)(long)i, 0);
|
|
|
|
kfree(dir_name);
|
|
}
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump;
|
|
|
|
for (i = 0; i < abox_dbg_dump_count; i++) {
|
|
dir_name = kasprintf(GFP_KERNEL, dir_fmt, i);
|
|
dir = abox_proc_mkdir(dir_name, NULL);
|
|
p_dump = &(*p_abox_dbg_dump_min)[i];
|
|
info = &abox_dbg_dump_info[i];
|
|
|
|
info->sram.data = p_dump->sram.dump;
|
|
info->sram.size = sizeof(p_dump->sram.dump);
|
|
abox_proc_create_bin("sram", 0440, dir, &info->sram);
|
|
|
|
info->log.data = p_dump->log.dump;
|
|
info->log.size = ABOX_LOG_SIZE;
|
|
abox_proc_create_bin("log", 0444, dir, &info->log);
|
|
|
|
info->log_01.data = p_dump->log_01.dump;
|
|
info->log_01.size = ABOX_LOG_SIZE;
|
|
abox_proc_create_bin("log-01", 0444, dir, &info->log_01);
|
|
|
|
info->sfr.data = p_dump->sfr.dump;
|
|
info->sfr.size = sizeof(p_dump->sfr.dump);
|
|
abox_proc_create_bin("sfr", 0440, dir, &info->sfr);
|
|
|
|
info->gicd.data = p_dump->sfr_gic_gicd;
|
|
info->gicd.size = sizeof(p_dump->sfr_gic_gicd);
|
|
abox_proc_create_bin("gicd", 0440, dir, &info->gicd);
|
|
|
|
info->gpr.data = p_dump->gpr;
|
|
info->gpr.size = sizeof(p_dump->gpr);
|
|
abox_proc_create_bin("gpr", 0440, dir, &info->gpr);
|
|
|
|
abox_proc_create_u64("time", 0440, dir, &p_dump->time);
|
|
|
|
info->reason.data = p_dump->reason;
|
|
info->reason.size = sizeof(p_dump->reason);
|
|
abox_proc_create_bin("reason", 0440, dir,
|
|
&info->reason);
|
|
|
|
abox_proc_create_u32("previous", 0440, dir,
|
|
&p_dump->previous_mem);
|
|
|
|
abox_proc_create_file("valid", 0440, dir,
|
|
&abox_dbg_fops_valid,
|
|
(void *)(long)i, 0);
|
|
|
|
abox_proc_create_file("clear", 0440, dir,
|
|
&abox_dbg_fops_clear,
|
|
(void *)(long)i, 0);
|
|
|
|
kfree(dir_name);
|
|
}
|
|
} else {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void abox_dbg_rmem_init(struct abox_data *data)
|
|
{
|
|
struct device *dev_abox = data->dev;
|
|
int i;
|
|
|
|
abox_info(dev_abox, "%s: size=%pa\n", __func__, &abox_dbg_rmem->size);
|
|
|
|
if (sizeof(*p_abox_dbg_dump) <= abox_dbg_rmem->size) {
|
|
struct abox_dbg_dump *p_dump;
|
|
|
|
p_abox_dbg_dump = rmem_vmap(abox_dbg_rmem);
|
|
data->dump_base = p_abox_dbg_dump;
|
|
for (i = 0; i < ABOX_DBG_DUMP_COUNT; i++) {
|
|
p_dump = &(*p_abox_dbg_dump)[i];
|
|
if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) {
|
|
p_dump->previous_gpr++;
|
|
p_dump->previous_mem++;
|
|
} else {
|
|
p_dump->previous_gpr = 0;
|
|
p_dump->previous_mem = 0;
|
|
}
|
|
}
|
|
|
|
abox_dbg_dump_count = ABOX_DBG_DUMP_COUNT;
|
|
p_dump_gpr_from_addr = dump_gpr_from_addr_full;
|
|
p_dump_gpr = dump_gpr_full;
|
|
p_dump_mem = dump_mem_full;
|
|
abox_info(dev_abox, "%s debug dump\n", "full");
|
|
} else if (sizeof((*p_abox_dbg_dump)[0]) <= abox_dbg_rmem->size) {
|
|
struct abox_dbg_dump *p_dump;
|
|
|
|
p_abox_dbg_dump = rmem_vmap(abox_dbg_rmem);
|
|
data->dump_base = p_abox_dbg_dump;
|
|
p_dump = &(*p_abox_dbg_dump)[0];
|
|
if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) {
|
|
p_dump->previous_gpr++;
|
|
p_dump->previous_mem++;
|
|
} else {
|
|
p_dump->previous_gpr = 0;
|
|
p_dump->previous_mem = 0;
|
|
}
|
|
|
|
abox_dbg_dump_count = 1;
|
|
p_dump_gpr_from_addr = dump_gpr_from_addr_half;
|
|
p_dump_gpr = dump_gpr_half;
|
|
p_dump_mem = dump_mem_half;
|
|
abox_info(dev_abox, "%s debug dump\n", "half");
|
|
} else if (sizeof(*p_abox_dbg_dump_min) <= abox_dbg_rmem->size) {
|
|
struct abox_dbg_dump_min *p_dump;
|
|
|
|
p_abox_dbg_dump_min = rmem_vmap(abox_dbg_rmem);
|
|
data->dump_base = p_abox_dbg_dump_min;
|
|
for (i = 0; i < ABOX_DBG_DUMP_COUNT; i++) {
|
|
p_dump = &(*p_abox_dbg_dump_min)[i];
|
|
if (p_dump->sfr.magic == ABOX_DBG_DUMP_MAGIC_SFR) {
|
|
p_dump->previous_gpr++;
|
|
p_dump->previous_mem++;
|
|
} else {
|
|
p_dump->previous_gpr = 0;
|
|
p_dump->previous_mem = 0;
|
|
}
|
|
}
|
|
|
|
abox_dbg_dump_count = ABOX_DBG_DUMP_COUNT;
|
|
p_dump_gpr_from_addr = dump_gpr_from_addr_min;
|
|
p_dump_gpr = dump_gpr_min;
|
|
p_dump_mem = dump_mem_min;
|
|
mutex_init(&lock);
|
|
abox_info(dev_abox, "%s debug dump\n", "min");
|
|
}
|
|
|
|
abox_dbg_dump_create_file(data);
|
|
|
|
data->dump_phys = abox_dbg_rmem->base;
|
|
abox_iommu_map(dev_abox, IOVA_DUMP_BUFFER, abox_dbg_rmem->base,
|
|
abox_dbg_rmem->size, data->dump_base);
|
|
}
|
|
|
|
void abox_dbg_dump_gpr_from_addr(struct device *dev, struct abox_data *data,
|
|
unsigned int *addr, enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
|
|
unsigned long long time = sched_clock();
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (pm_runtime_suspended(data->dev)) {
|
|
abox_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
|
|
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
|
|
called[src] = time;
|
|
return;
|
|
}
|
|
called[src] = time;
|
|
|
|
if (p_dump_gpr_from_addr)
|
|
p_dump_gpr_from_addr(dev, addr, src, reason, time);
|
|
else
|
|
abox_err(dev, "abox dbg isn't initialized\n");
|
|
}
|
|
|
|
void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
|
|
unsigned long long time = sched_clock();
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (pm_runtime_suspended(data->dev)) {
|
|
abox_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
|
|
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
|
|
called[src] = time;
|
|
return;
|
|
}
|
|
called[src] = time;
|
|
|
|
if (p_dump_gpr)
|
|
p_dump_gpr(dev, data, src, reason, time);
|
|
else
|
|
abox_err(dev, "abox dbg isn't initialized\n");
|
|
}
|
|
|
|
struct dump_mem_work_struct {
|
|
struct work_struct work;
|
|
struct device *dev;
|
|
struct abox_data *data;
|
|
enum abox_dbg_dump_src src;
|
|
const char *reason;
|
|
unsigned long long time;
|
|
};
|
|
|
|
static void dump_mem_work_func(struct work_struct *work)
|
|
{
|
|
struct dump_mem_work_struct *dmw;
|
|
|
|
dmw = container_of(work, typeof(*dmw), work);
|
|
p_dump_mem(dmw->dev, dmw->data, dmw->src, dmw->reason, dmw->time);
|
|
}
|
|
|
|
static void dump_mem(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason, bool atomic)
|
|
{
|
|
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
|
|
unsigned long long time = sched_clock();
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (pm_runtime_suspended(data->dev)) {
|
|
abox_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
|
|
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
|
|
called[src] = time;
|
|
return;
|
|
}
|
|
called[src] = time;
|
|
|
|
if (p_dump_mem) {
|
|
if (atomic) {
|
|
p_dump_mem(dev, data, src, reason, time);
|
|
} else {
|
|
struct dump_mem_work_struct dmw;
|
|
|
|
INIT_WORK_ONSTACK(&dmw.work, dump_mem_work_func);
|
|
dmw.dev = dev;
|
|
dmw.data = data;
|
|
dmw.src = src;
|
|
dmw.reason = reason;
|
|
dmw.time = time;
|
|
queue_work(system_unbound_wq, &dmw.work);
|
|
flush_work(&dmw.work);
|
|
destroy_work_on_stack(&dmw.work);
|
|
}
|
|
} else {
|
|
abox_err(dev, "abox dbg isn't initialized\n");
|
|
}
|
|
}
|
|
|
|
void abox_dbg_dump_mem(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
dump_mem(dev, data, src, reason, true);
|
|
}
|
|
|
|
void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
abox_dbg_dump_gpr(dev, data, src, reason);
|
|
dump_mem(dev, data, src, reason, false);
|
|
}
|
|
|
|
struct abox_dbg_dump_simple {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_log log;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[SZ_128];
|
|
struct abox_dbg_dump_sfr atune;
|
|
long long time;
|
|
char reason[SZ_32];
|
|
} __packed;
|
|
|
|
static struct abox_dbg_dump_simple abox_dump_simple;
|
|
|
|
void abox_dbg_dump_simple(struct device *dev, struct abox_data *data,
|
|
const char *reason)
|
|
{
|
|
static unsigned long long called;
|
|
unsigned long long time = sched_clock();
|
|
|
|
abox_info(dev, "%s\n", __func__);
|
|
|
|
if (pm_runtime_suspended(data->dev)) {
|
|
abox_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (called && time - called < ABOX_DBG_DUMP_LIMIT_NS) {
|
|
dev_dbg_ratelimited(dev, "%s: skipped\n", __func__);
|
|
called = time;
|
|
return;
|
|
}
|
|
called = time;
|
|
|
|
abox_dump_simple.time = time;
|
|
strncpy(abox_dump_simple.reason, reason,
|
|
sizeof(abox_dump_simple.reason) - 1);
|
|
abox_core_dump_gpr(abox_dump_simple.gpr);
|
|
memcpy_fromio(abox_dump_simple.sram.dump, data->sram_base,
|
|
SRAM_FIRMWARE_SIZE);
|
|
abox_dump_simple.sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy(abox_dump_simple.log.dump, data->dram_base + ABOX_LOG_OFFSET,
|
|
ABOX_LOG_SIZE);
|
|
abox_dump_simple.log.magic = ABOX_DBG_DUMP_MAGIC_LOG;
|
|
memcpy_fromio(abox_dump_simple.sfr.dump, data->sfr_base,
|
|
sizeof(abox_dump_simple.sfr.dump));
|
|
abox_dump_simple.sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(abox_dump_simple.atune.dump,
|
|
data->sfr_base + ATUNE_OFFSET,
|
|
sizeof(abox_dump_simple.atune.dump));
|
|
abox_dump_simple.atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE;
|
|
abox_gicd_dump(data->dev_gic, (char *)abox_dump_simple.sfr_gic_gicd, 0,
|
|
sizeof(abox_dump_simple.sfr_gic_gicd));
|
|
}
|
|
|
|
static struct abox_dbg_dump_simple abox_dump_suspend;
|
|
|
|
void abox_dbg_dump_suspend(struct device *dev, struct abox_data *data)
|
|
{
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
abox_dump_suspend.time = sched_clock();
|
|
strncpy(abox_dump_suspend.reason, "suspend",
|
|
sizeof(abox_dump_suspend.reason) - 1);
|
|
abox_core_dump_gpr(abox_dump_suspend.gpr);
|
|
memcpy_fromio(abox_dump_suspend.sram.dump, data->sram_base,
|
|
SRAM_FIRMWARE_SIZE);
|
|
abox_dump_suspend.sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy(abox_dump_suspend.log.dump, data->dram_base + ABOX_LOG_OFFSET,
|
|
ABOX_LOG_SIZE);
|
|
abox_dump_suspend.log.magic = ABOX_DBG_DUMP_MAGIC_LOG;
|
|
memcpy_fromio(abox_dump_suspend.sfr.dump, data->sfr_base,
|
|
sizeof(abox_dump_suspend.sfr.dump));
|
|
abox_dump_suspend.sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(abox_dump_suspend.atune.dump,
|
|
data->sfr_base + ATUNE_OFFSET,
|
|
sizeof(abox_dump_suspend.atune.dump));
|
|
abox_dump_suspend.atune.magic = ABOX_DBG_DUMP_MAGIC_ATUNE;
|
|
abox_gicd_dump(data->dev_gic, (char *)abox_dump_suspend.sfr_gic_gicd, 0,
|
|
sizeof(abox_dump_suspend.sfr_gic_gicd));
|
|
}
|
|
|
|
static atomic_t abox_error_count = ATOMIC_INIT(0);
|
|
|
|
void abox_dbg_report_status(struct device *dev, bool ok)
|
|
{
|
|
char env[32] = {0,};
|
|
char *envp[2] = {env, NULL};
|
|
|
|
abox_info(dev, "%s\n", __func__);
|
|
|
|
if (ok)
|
|
atomic_set(&abox_error_count, 0);
|
|
else
|
|
atomic_inc(&abox_error_count);
|
|
|
|
snprintf(env, sizeof(env), "ERR_CNT=%d",
|
|
atomic_read(&abox_error_count));
|
|
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
|
|
int abox_dbg_get_error_count(struct device *dev)
|
|
{
|
|
int count = atomic_read(&abox_error_count);
|
|
|
|
abox_dbg(dev, "%s: %d\n", __func__, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t calliope_sram_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct device *dev_abox = dev->parent;
|
|
|
|
abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
pm_runtime_get(dev_abox);
|
|
if (off == 0)
|
|
abox_core_flush();
|
|
memcpy_fromio(buf, battr->private + off, size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(buf, 0x0, size);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t calliope_dram_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct device *dev_abox = dev->parent;
|
|
|
|
abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
|
|
|
|
/* if the area isn't existed, private(=base address) is 0 */
|
|
if (!battr->private)
|
|
return 0;
|
|
|
|
if (!pm_runtime_suspended(dev_abox)) {
|
|
pm_runtime_get(dev_abox);
|
|
if (off == 0)
|
|
abox_core_flush();
|
|
pm_runtime_put(dev_abox);
|
|
}
|
|
memcpy(buf, battr->private + off, size);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t calliope_log_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
return calliope_dram_read(file, kobj, battr, buf, off, size);
|
|
}
|
|
|
|
static ssize_t calliope_slog_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
return calliope_dram_read(file, kobj, battr, buf, off, size);
|
|
}
|
|
|
|
static ssize_t gicd_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct device *dev_abox = dev->parent;
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
|
|
abox_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
|
|
|
|
pm_runtime_get(dev_abox);
|
|
abox_gicd_dump(data->dev_gic, buf, off, size);
|
|
pm_runtime_put(dev_abox);
|
|
|
|
return size;
|
|
}
|
|
|
|
/* size will be updated later */
|
|
static BIN_ATTR(calliope_sram, 0440, calliope_sram_read, NULL, 0);
|
|
static BIN_ATTR(calliope_dram, 0440, calliope_dram_read, NULL,
|
|
DRAM_FIRMWARE_SIZE);
|
|
static BIN_ATTR_RO(calliope_log, ABOX_LOG_SIZE);
|
|
static BIN_ATTR(calliope_slog, 0440, calliope_slog_read, NULL, 0);
|
|
static BIN_ATTR(gicd, 0440, gicd_read, NULL, SZ_4K);
|
|
static struct bin_attribute *calliope_bin_attrs[] = {
|
|
&bin_attr_calliope_sram,
|
|
&bin_attr_calliope_dram,
|
|
&bin_attr_calliope_log,
|
|
&bin_attr_calliope_slog,
|
|
&bin_attr_gicd,
|
|
};
|
|
|
|
static ssize_t gpr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct device *dev_abox = dev->parent;
|
|
|
|
if (pm_runtime_suspended(dev_abox)) {
|
|
abox_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return abox_core_show_gpr(buf);
|
|
}
|
|
|
|
static DEVICE_ATTR(gpr, 0440, gpr_show, NULL);
|
|
|
|
struct abox_dbg_file_info {
|
|
struct proc_dir_entry *file;
|
|
struct abox_data *abox_data;
|
|
const char *name;
|
|
const struct proc_ops *fops;
|
|
void __iomem *io_base;
|
|
void *base;
|
|
void *buf;
|
|
size_t size;
|
|
};
|
|
|
|
static ssize_t abox_dbg_file_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
|
|
return simple_read_from_buffer(buf, count, pos, info->buf, info->size);
|
|
}
|
|
|
|
static int abox_dbg_file_open_sram(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
struct device *dev_abox = info->abox_data->dev;
|
|
|
|
info->buf = vmalloc(info->size);
|
|
if (!info->buf)
|
|
return -ENOMEM;
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
abox_core_flush();
|
|
memcpy_fromio(info->buf, info->io_base, info->size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(info->buf, 0x0, info->size);
|
|
}
|
|
|
|
return simple_open(inode, file);
|
|
}
|
|
|
|
static int abox_dbg_file_open_dram(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
struct device *dev_abox = info->abox_data->dev;
|
|
|
|
info->buf = vmalloc(info->size);
|
|
if (!info->buf)
|
|
return -ENOMEM;
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
abox_core_flush();
|
|
memcpy(info->buf, info->base, info->size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memcpy(info->buf, info->base, info->size);
|
|
}
|
|
|
|
return simple_open(inode, file);
|
|
}
|
|
|
|
static int abox_dbg_file_open_sfr(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
struct device *dev_abox = info->abox_data->dev;
|
|
|
|
info->buf = vmalloc(info->size);
|
|
if (!info->buf)
|
|
return -ENOMEM;
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
memcpy_fromio(info->buf, info->io_base, info->size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(info->buf, 0x0, info->size);
|
|
}
|
|
|
|
return simple_open(inode, file);
|
|
}
|
|
|
|
static int abox_dbg_file_open_gpr(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
struct device *dev_abox = info->abox_data->dev;
|
|
|
|
info->buf = vmalloc(info->size);
|
|
if (!info->buf)
|
|
return -ENOMEM;
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
abox_core_show_gpr(info->buf);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(info->buf, 0x0, info->size);
|
|
}
|
|
|
|
return simple_open(inode, file);
|
|
}
|
|
|
|
static int abox_dbg_file_open_gicd(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
struct device *dev_abox = info->abox_data->dev;
|
|
|
|
info->buf = vmalloc(info->size);
|
|
if (!info->buf)
|
|
return -ENOMEM;
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
abox_gicd_dump(info->abox_data->dev_gic, info->buf, 0,
|
|
info->size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(info->buf, 0x0, info->size);
|
|
}
|
|
|
|
return simple_open(inode, file);
|
|
}
|
|
|
|
static int abox_dbg_file_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_dbg_file_info *info = abox_proc_data(file);
|
|
|
|
vfree(info->buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct proc_ops abox_dbg_file_fops_sram = {
|
|
.proc_read = abox_dbg_file_read,
|
|
.proc_open = abox_dbg_file_open_sram,
|
|
.proc_release = abox_dbg_file_release,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static const struct proc_ops abox_dbg_file_fops_dram = {
|
|
.proc_read = abox_dbg_file_read,
|
|
.proc_open = abox_dbg_file_open_dram,
|
|
.proc_release = abox_dbg_file_release,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static const struct proc_ops abox_dbg_file_fops_sfr = {
|
|
.proc_read = abox_dbg_file_read,
|
|
.proc_open = abox_dbg_file_open_sfr,
|
|
.proc_release = abox_dbg_file_release,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static const struct proc_ops abox_dbg_file_fops_gpr = {
|
|
.proc_read = abox_dbg_file_read,
|
|
.proc_open = abox_dbg_file_open_gpr,
|
|
.proc_release = abox_dbg_file_release,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static const struct proc_ops abox_dbg_file_fops_gicd = {
|
|
.proc_read = abox_dbg_file_read,
|
|
.proc_open = abox_dbg_file_open_gicd,
|
|
.proc_release = abox_dbg_file_release,
|
|
.proc_lseek = default_llseek,
|
|
};
|
|
|
|
static struct abox_dbg_file_info sram_info = {
|
|
.name = "sram",
|
|
.fops = &abox_dbg_file_fops_sram,
|
|
};
|
|
static struct abox_dbg_file_info dram_info = {
|
|
.name = "dram",
|
|
.fops = &abox_dbg_file_fops_dram,
|
|
};
|
|
static struct abox_dbg_file_info log_info = {
|
|
.name = "log",
|
|
.fops = &abox_dbg_file_fops_dram,
|
|
};
|
|
static struct abox_dbg_file_info slog_info = {
|
|
.name = "slog",
|
|
.fops = &abox_dbg_file_fops_dram,
|
|
};
|
|
static struct abox_dbg_file_info sfr_info = {
|
|
.name = "sfr",
|
|
.fops = &abox_dbg_file_fops_sfr,
|
|
};
|
|
static struct abox_dbg_file_info gpr_info = {
|
|
.name = "gpr",
|
|
.fops = &abox_dbg_file_fops_gpr,
|
|
};
|
|
static struct abox_dbg_file_info gicd_info = {
|
|
.name = "gicd",
|
|
.fops = &abox_dbg_file_fops_gicd,
|
|
};
|
|
|
|
static int abox_dbg_create_file(struct abox_dbg_file_info *info,
|
|
struct proc_dir_entry *parent, struct abox_data *data,
|
|
void __iomem *io_base, void *base, size_t size)
|
|
{
|
|
info->abox_data = data;
|
|
info->io_base = io_base;
|
|
info->base = base;
|
|
info->size = size;
|
|
info->file = abox_proc_create_file(info->name, 0444, parent, info->fops,
|
|
info, info->size);
|
|
|
|
return PTR_ERR_OR_ZERO(info->file);
|
|
}
|
|
|
|
static void abox_dbg_create_files(struct abox_data *data)
|
|
{
|
|
struct proc_dir_entry *dir;
|
|
|
|
dir = abox_proc_mkdir("runtime", NULL);
|
|
|
|
abox_dbg_create_file(&sram_info, dir, data, data->sram_base,
|
|
NULL, SRAM_FIRMWARE_SIZE);
|
|
abox_dbg_create_file(&dram_info, dir, data, NULL,
|
|
data->dram_base, DRAM_FIRMWARE_SIZE);
|
|
abox_dbg_create_file(&log_info, dir, data, NULL,
|
|
data->dram_base + ABOX_LOG_OFFSET, ABOX_LOG_SIZE);
|
|
abox_dbg_create_file(&slog_info, dir, data, NULL,
|
|
data->slog_base, data->slog_size);
|
|
abox_dbg_create_file(&sfr_info, dir, data, data->sfr_base,
|
|
NULL, data->sfr_size);
|
|
abox_dbg_create_file(&gpr_info, dir, data, NULL,
|
|
NULL, PAGE_SIZE);
|
|
abox_dbg_create_file(&gicd_info, dir, data, NULL,
|
|
NULL, PAGE_SIZE);
|
|
}
|
|
|
|
struct abox_dbg_memlog_entity {
|
|
const char *name;
|
|
struct memlog_obj *fobj;
|
|
struct memlog_obj *mobj;
|
|
enum abox_region rid;
|
|
size_t buf_size;
|
|
void *src_buf;
|
|
};
|
|
|
|
static struct abox_dbg_memlog_entity abox_dbg_memlogs[] = {
|
|
{ .name = "sram", .rid = ABOX_REG_SRAM, },
|
|
{ .name = "dram", .rid = ABOX_REG_DRAM,
|
|
.buf_size = DRAM_FIRMWARE_SIZE },
|
|
{ .name = "log", .rid = ABOX_REG_LOG, .buf_size = ABOX_LOG_SIZE },
|
|
{ .name = "slog", .rid = ABOX_REG_SLOG, },
|
|
{ .name = "sfr", .rid = ABOX_REG_SFR, },
|
|
{ .name = "gpr", .rid = ABOX_REG_GPR, .buf_size = SZ_128 },
|
|
{ .name = "gicd", .rid = ABOX_REG_GICD,
|
|
.buf_size = (SZ_4K / sizeof(u32))},
|
|
};
|
|
|
|
static void abox_dbg_unregister_memlog(struct abox_data *data)
|
|
{
|
|
struct device *dev = data->dev;
|
|
size_t len = ARRAY_SIZE(abox_dbg_memlogs);
|
|
struct abox_dbg_memlog_entity *me;
|
|
|
|
for (me = abox_dbg_memlogs; me - abox_dbg_memlogs < len; me++) {
|
|
if (me->fobj)
|
|
memlog_free(me->fobj);
|
|
|
|
if (me->rid < ABOX_REG_DEVICE_LAST) {
|
|
if (me->mobj)
|
|
memlog_free(me->mobj);
|
|
} else if (me->rid < ABOX_REG_INT_LAST) {
|
|
if (me->src_buf)
|
|
devm_kfree(dev, me->src_buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int abox_dbg_register_memlog(struct abox_data *data)
|
|
{
|
|
phys_addr_t paddr;
|
|
char name[32] = {0,};
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(abox_dbg_memlogs); i++) {
|
|
snprintf(name, sizeof(name), "%s-file",
|
|
abox_dbg_memlogs[i].name);
|
|
paddr = (phys_addr_t)abox_get_resource_info(data,
|
|
abox_dbg_memlogs[i].rid,
|
|
false, &abox_dbg_memlogs[i].buf_size);
|
|
abox_dbg_memlogs[i].fobj = memlog_alloc_file(data->drvlog_desc,
|
|
name, abox_dbg_memlogs[i].buf_size,
|
|
abox_dbg_memlogs[i].buf_size, 20, 10);
|
|
if (!abox_dbg_memlogs[i].fobj) {
|
|
abox_err(data->dev, "Failed to allocate file for %s\n",
|
|
name);
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
snprintf(name, sizeof(name), "%s-mem",
|
|
abox_dbg_memlogs[i].name);
|
|
if (abox_dbg_memlogs[i].rid < ABOX_REG_DEVICE_LAST) {
|
|
abox_dbg_memlogs[i].mobj =
|
|
memlog_alloc_dump(data->drvlog_desc,
|
|
abox_dbg_memlogs[i].buf_size, paddr,
|
|
true, abox_dbg_memlogs[i].fobj, name);
|
|
if (!abox_dbg_memlogs[i].mobj) {
|
|
abox_err(data->dev, "Fail:alloc mem for %s\n",
|
|
name);
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
} else if (abox_dbg_memlogs[i].rid < ABOX_REG_INT_LAST) {
|
|
abox_dbg_memlogs[i].src_buf = devm_kzalloc(data->dev,
|
|
abox_dbg_memlogs[i].buf_size,
|
|
GFP_KERNEL);
|
|
if (!abox_dbg_memlogs[i].src_buf) {
|
|
abox_err(data->dev, "Fail:alloc mem for %s\n",
|
|
name);
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
} else {
|
|
abox_dbg_memlogs[i].src_buf =
|
|
abox_get_resource_info(data,
|
|
abox_dbg_memlogs[i].rid,
|
|
true, NULL);
|
|
if (!abox_dbg_memlogs[i].src_buf) {
|
|
abox_err(data->dev, "Fail:alloc mem for %s\n",
|
|
name);
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
abox_dbg_unregister_memlog(data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void abox_dbg_dump_memlog(struct abox_data *data)
|
|
{
|
|
int i;
|
|
size_t size;
|
|
|
|
/* dump only if power is on */
|
|
if (!pm_runtime_get_if_in_use(data->dev)) {
|
|
abox_info(data->dev, "memlog skip: no power\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(abox_dbg_memlogs); i++) {
|
|
if (abox_dbg_memlogs[i].rid < ABOX_REG_DEVICE_LAST) {
|
|
if (abox_dbg_memlogs[i].mobj)
|
|
memlog_do_dump(abox_dbg_memlogs[i].mobj,
|
|
MEMLOG_LEVEL_EMERG);
|
|
} else {
|
|
if (abox_dbg_memlogs[i].rid == ABOX_REG_GPR)
|
|
abox_core_dump_gpr(
|
|
(unsigned int *)abox_dbg_memlogs[i].src_buf);
|
|
else if (abox_dbg_memlogs[i].rid == ABOX_REG_GICD)
|
|
abox_gicd_dump(data->dev_gic,
|
|
(char *)abox_dbg_memlogs[i].src_buf, 0,
|
|
abox_dbg_memlogs[i].buf_size);
|
|
if (abox_dbg_memlogs[i].fobj) {
|
|
size = memlog_write_file(abox_dbg_memlogs[i].fobj,
|
|
abox_dbg_memlogs[i].src_buf,
|
|
abox_dbg_memlogs[i].buf_size);
|
|
if (size <= 0)
|
|
abox_err(data->dev, "%s: Failed to write memlog: %zu\n",
|
|
abox_dbg_memlogs[i].name, size);
|
|
}
|
|
}
|
|
}
|
|
memlog_sync_to_file(data->drv_log_obj);
|
|
|
|
pm_runtime_put(data->dev);
|
|
}
|
|
|
|
static int samsung_abox_debug_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device *dev_abox = dev->parent;
|
|
struct abox_data *data = dev_get_drvdata(dev_abox);
|
|
ssize_t new_size;
|
|
int i, ret;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
if (!abox_dbg_rmem) {
|
|
struct device_node *np_tmp;
|
|
|
|
np_tmp = of_parse_phandle(dev->of_node, "memory-region", 0);
|
|
if (np_tmp)
|
|
abox_dbg_rmem = of_reserved_mem_lookup(np_tmp);
|
|
}
|
|
|
|
if (abox_dbg_rmem) {
|
|
new_size = abox_oem_resize_reserved_memory(ABOX_OEM_RESERVED_MEMORY_DBG);
|
|
if (new_size >= 0)
|
|
abox_dbg_resize_rmem(dev, abox_dbg_rmem, new_size, "abox_dbg");
|
|
|
|
abox_dbg_rmem_init(data);
|
|
}
|
|
|
|
if (!abox_dbg_slog) {
|
|
struct device_node *np_tmp;
|
|
|
|
np_tmp = of_parse_phandle(dev->of_node, "memory-region", 1);
|
|
if (np_tmp)
|
|
abox_dbg_slog = of_reserved_mem_lookup(np_tmp);
|
|
}
|
|
|
|
if (abox_dbg_slog) {
|
|
new_size = abox_oem_resize_reserved_memory(ABOX_OEM_RESERVED_MEMORY_SLOG);
|
|
if (new_size >= 0)
|
|
abox_dbg_resize_rmem(dev, abox_dbg_slog, new_size, "abox_slog");
|
|
|
|
abox_dbg_slog_init(data);
|
|
}
|
|
|
|
ret = abox_dbg_register_memlog(data);
|
|
if (ret < 0)
|
|
dev_warn(dev, "Failed to register memlog: %d\n", ret);
|
|
|
|
abox_dbg_create_files(data);
|
|
|
|
ret = device_create_file(dev, &dev_attr_gpr);
|
|
if (ret < 0)
|
|
abox_warn(dev, "Failed to create file: %s\n",
|
|
dev_attr_gpr.attr.name);
|
|
bin_attr_calliope_sram.size = SRAM_FIRMWARE_SIZE;
|
|
bin_attr_calliope_sram.private = data->sram_base;
|
|
bin_attr_calliope_dram.private = data->dram_base;
|
|
bin_attr_calliope_log.private = data->dram_base + ABOX_LOG_OFFSET;
|
|
if (data->slog_size >= ABOX_SLOG_DATA_OFFSET) {
|
|
bin_attr_calliope_slog.size = data->slog_size - ABOX_SLOG_DATA_OFFSET;
|
|
bin_attr_calliope_slog.private = data->slog_base + ABOX_SLOG_DATA_OFFSET;
|
|
} else {
|
|
bin_attr_calliope_slog.size = data->slog_size;
|
|
bin_attr_calliope_slog.private = data->slog_base;
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(calliope_bin_attrs); i++) {
|
|
struct bin_attribute *battr = calliope_bin_attrs[i];
|
|
|
|
ret = device_create_bin_file(dev, battr);
|
|
if (ret < 0)
|
|
abox_warn(dev, "Failed to create file: %s\n",
|
|
battr->attr.name);
|
|
}
|
|
|
|
abox_proc_symlink_dev(dev, "debug");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int samsung_abox_debug_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id samsung_abox_debug_match[] = {
|
|
{
|
|
.compatible = "samsung,abox-debug",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, samsung_abox_debug_match);
|
|
|
|
struct platform_driver samsung_abox_debug_driver = {
|
|
.probe = samsung_abox_debug_probe,
|
|
.remove = samsung_abox_debug_remove,
|
|
.driver = {
|
|
.name = "abox-debug",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(samsung_abox_debug_match),
|
|
},
|
|
};
|