blob: 41a570193238ebdf516849df11e68f552433e144 [file] [log] [blame]
/*
* Samsung Exynos SoC series NPU driver
*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "npu-common.h"
#include "npu-log.h"
#include "npu-debug.h"
#include "npu-system.h"
#include <linux/atomic.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
/* Maker structure for each memdump */
struct mem_dump_stats {
u32 readpos;
};
/* Global data storage for memory dump - Initialized from probe function */
struct {
atomic_t registered;
struct npu_iomem_area tcu_sram;
struct npu_iomem_area idp_sram;
} npu_memdump;
void *npu_ram_dump_addr;
////////////////////////////////////////////////////////////////////////////////////////
// Firmware log(on SRAM) dump
/* Position of log buffer from start of NPU_IOMEM_SRAM_START */
static const u32 MEM_LOG_OFFSET = 0x78000;
static const u32 MEM_LOG_SIZE = 0x8000;
static const char *FW_MEM_LOG_NAME = "fw-log-SRAM";
#ifdef CONFIG_EXYNOS_NPU_DEBUG_SRAM_DUMP
static const char *TCU_SRAM_DUMP_SYSFS_NAME = "SRAM-TCU";
static const char *IDP_SRAM_DUMP_SYSFS_NAME = "SRAM-IDP";
#endif
#define NPU_SRAM_DUMP_BUF (1024 * 1024)
static int fw_mem_log_open_fops(struct inode *inode, struct file *file)
{
int ret;
struct mem_dump_stats *stats;
stats = kmalloc(sizeof(*stats), GFP_KERNEL);
if (!stats) {
npu_err("could not allocate memory for stat object.\n");
ret = -ENOMEM;
goto p_err;
}
/* Save it as a private data */
memset(stats, 0, sizeof(*stats));
file->private_data = stats;
return 0;
p_err:
if (stats) {
kfree(stats);
}
return ret;
}
static ssize_t fw_mem_log_read_fops(struct file *file, char __user *buf, size_t size, loff_t *off)
{
char *p;
char ch;
size_t copied = 0;
struct mem_dump_stats *stats;
BUG_ON(!file->private_data);
BUG_ON(!npu_memdump.tcu_sram.vaddr);
stats = (struct mem_dump_stats *)file->private_data;
if (stats->readpos >= MEM_LOG_SIZE) {
return 0; /* EOF */
}
p = npu_memdump.tcu_sram.vaddr + MEM_LOG_OFFSET + stats->readpos;
if (*p == '\0') {
return 0; /* No more data */
}
while ((*p != '\0') && (stats->readpos < MEM_LOG_SIZE) && (copied < size)) {
/* Copy to user buffer */
get_user(ch, p);
put_user(ch, &buf[copied]);
/* Adjust pointer */
p++;
stats->readpos++;
copied++;
}
return copied;
}
static int fw_mem_log_close_fops(struct inode *inode, struct file *file)
{
struct mem_dump_stats *stats;
stats = (struct mem_dump_stats *)file->private_data;
if (stats) {
kfree(stats);
}
return 0;
}
int ram_dump_fault_listner(struct npu_device *npu)
{
int ret = 0;
struct npu_system *system = &npu->system;
u32 *tcu_dump_addr = kzalloc(system->tcu_sram.size, GFP_ATOMIC);
u32 *idp_dump_addr = kzalloc(system->idp_sram.size, GFP_ATOMIC);
if (tcu_dump_addr) {
memcpy_fromio(tcu_dump_addr, system->tcu_sram.vaddr, system->tcu_sram.size);
pr_err("NPU TCU SRAM dump - %pK / %paB\n", tcu_dump_addr, &system->tcu_sram.size);
} else {
pr_err("tcu_dump_addr is NULL\n");
ret= -ENOMEM;
goto exit_err;
}
if (idp_dump_addr) {
memcpy_fromio(idp_dump_addr, system->idp_sram.vaddr, system->idp_sram.size);
pr_err("NPU IDP SRAM dump - %pK / %paB\n", idp_dump_addr, &system->idp_sram.size);
} else {
pr_err("idp_dump_addr is NULL\n");
ret = -ENOMEM;
goto exit_err;
}
/* tcu_dump_addr and idp_dump_addr are not freed, because we expect them left on ramdump */
return ret;
exit_err:
if (tcu_dump_addr)
kfree(tcu_dump_addr);
return ret;
}
int npu_util_dump_handle_error_k(struct npu_device *device)
{
int ret = 0;
proto_req_fault_listener();
mbx_rslt_fault_listener();
ret = ram_dump_fault_listner(device);
if (ret) {
pr_err("failed in ram_dump_fault_listner\n");
}
session_fault_listener();
return ret;
}
static struct file_operations fw_mem_log_fops = {
.owner = THIS_MODULE,
.open = fw_mem_log_open_fops,
.release = fw_mem_log_close_fops,
.read = fw_mem_log_read_fops,
};
#ifdef CONFIG_EXYNOS_NPU_DEBUG_SRAM_DUMP
static ssize_t __npu_sram_dump_write(
const char *inbuf, size_t count, size_t *offset, size_t *size)
{
int ret;
/* Parsing */
ret = sscanf(inbuf, "%zx.%zx", offset, size);
if (ret != 2) {
npu_err("Command line parsing error.\n");
return -EINVAL;
}
npu_info("SRAM dump setting : offset 0x%zx, size 0x%zx bytes\n", *offset, *size);
barrier();
return count;
}
static ssize_t __npu_sram_dump_read(
char __user *outbuf, size_t count, void __iomem *sram_vaddr, size_t sram_size)
{
ssize_t ret = 0;
u32 *tmp_buf = NULL;
size_t i;
tmp_buf = kzalloc(sram_size, GFP_KERNEL);
if (!tmp_buf) {
ret = -ENOMEM;
goto err_exit;
}
memcpy_fromio((void *)tmp_buf, sram_vaddr, sram_size);
for (i = 0; i < sram_size / sizeof(u32); i += 4) {
npu_info("SRAM %pK -> %pK : %08x %08x %08x %08x\n",
sram_vaddr + i * sizeof(u32), tmp_buf + i,
*(tmp_buf + i), *(tmp_buf + i + 1),
*(tmp_buf + i + 2), *(tmp_buf + i + 3));
}
if (access_ok(VERIFY_WRITE, outbuf, sram_size)) {
ret = copy_to_user(outbuf, (char *)tmp_buf, min(sram_size, count) / sizeof(char));
if (ret) {
npu_err("copy_to_user failed : %zd\n", ret);
goto err_exit;
}
npu_info("ret value : %zd (%zd)\n", ret, sram_size);
// return copied size (target size - uncopied size)
ret = sram_size - ret;
} else {
npu_info("buffer inaccessible : 0x%pK (%zd)\n", outbuf, sram_size);
// no copy, return target size
ret = sram_size;
}
err_exit:
if (tmp_buf)
kfree(tmp_buf);
return ret;
}
#define DECLARE_NPU_SRAM_DUMP(__NPU_SRAM) \
static size_t __NPU_SRAM##_sram_dump_offset; \
static size_t __NPU_SRAM##_sram_dump_size; \
static int __NPU_SRAM##_sram_dump_open(struct inode *inode, struct file *file) \
{ \
npu_info(#__NPU_SRAM " SRAM dump open\n"); \
return 0; \
} \
static ssize_t __NPU_SRAM##_sram_dump_read( \
struct file *file, char __user *buf, size_t count, loff_t *offp) \
{ \
BUG_ON(!npu_memdump.__NPU_SRAM##_sram.vaddr); \
npu_info(#__NPU_SRAM " SRAM dump : va 0x%pK + %zx (pa 0x%x + %zx), 0x%zx bytes\n", \
npu_memdump.__NPU_SRAM##_sram.vaddr, \
__NPU_SRAM##_sram_dump_offset, \
npu_memdump.__NPU_SRAM##_sram.paddr, \
__NPU_SRAM##_sram_dump_offset, \
__NPU_SRAM##_sram_dump_size); \
return __npu_sram_dump_read(buf, count, \
npu_memdump.__NPU_SRAM##_sram.vaddr + __NPU_SRAM##_sram_dump_offset, \
__NPU_SRAM##_sram_dump_size); \
} \
static ssize_t __NPU_SRAM##_sram_dump_write( \
struct file *file, const char * __user userbuf, size_t count, loff_t *offp) \
{ \
unsigned long remain; \
char inbuf[32]; \
remain = copy_from_user(inbuf, userbuf, min(ARRAY_SIZE(inbuf), count)); \
if (remain) { \
npu_err("Invalid input data\n"); \
return -EINVAL; \
} \
return __npu_sram_dump_write( \
inbuf, count, \
&__NPU_SRAM##_sram_dump_offset, \
&__NPU_SRAM##_sram_dump_size); \
} \
static int __NPU_SRAM##_sram_dump_close(struct inode *inode, struct file *file) \
{ \
npu_info(#__NPU_SRAM " SRAM dump close\n"); \
return 0; \
} \
static struct file_operations __NPU_SRAM##_sram_dump_fops = { \
.owner = THIS_MODULE, \
.open = __NPU_SRAM##_sram_dump_open, \
.release = __NPU_SRAM##_sram_dump_close, \
.read = __NPU_SRAM##_sram_dump_read, \
.write = __NPU_SRAM##_sram_dump_write, \
}\
#define NPU_SRAM_DUMP_FOPS(__NPU_SRAM) __NPU_SRAM##_sram_dump_fops
DECLARE_NPU_SRAM_DUMP(tcu);
DECLARE_NPU_SRAM_DUMP(idp);
#endif
////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle functions
int npu_util_memdump_probe(struct npu_system *system)
{
BUG_ON(!system);
BUG_ON(!system->tcu_sram.vaddr);
atomic_set(&npu_memdump.registered, 0);
npu_memdump.tcu_sram = system->tcu_sram;
npu_memdump.idp_sram = system->idp_sram;
probe_info("%s: paddr = %08x\n", FW_MEM_LOG_NAME,
system->tcu_sram.paddr + MEM_LOG_OFFSET
);
#ifdef CONFIG_EXYNOS_NPU_DEBUG_SRAM_DUMP
probe_info("%s: paddr = %08x\n", TCU_SRAM_DUMP_SYSFS_NAME,
system->tcu_sram.paddr);
tcu_sram_dump_size = system->tcu_sram.size;
probe_info("%s: paddr = %08x\n", IDP_SRAM_DUMP_SYSFS_NAME,
system->idp_sram.paddr);
idp_sram_dump_size = system->idp_sram.size;
#endif
return 0;
}
int npu_util_memdump_open(struct npu_system *system)
{
int loaded;
int ret;
BUG_ON(!npu_memdump.tcu_sram.vaddr);
npu_info("start in npu_util_memdump_open");
loaded = atomic_xchg(&npu_memdump.registered, 1);
if (loaded == 0) {
/* Initial load */
npu_info("register on debugfs: npu_util_memdump_open : Register on debugfs.");
ret = npu_debug_register(FW_MEM_LOG_NAME, &fw_mem_log_fops);
if (ret) {
npu_err("npu_debug_register error : ret = %d\n", ret);
goto p_err;
}
#ifdef CONFIG_EXYNOS_NPU_DEBUG_SRAM_DUMP
npu_info("register debugfs %s\n", TCU_SRAM_DUMP_SYSFS_NAME);
ret = npu_debug_register(TCU_SRAM_DUMP_SYSFS_NAME, &NPU_SRAM_DUMP_FOPS(tcu));
if (ret) {
npu_err("%s npu_debug_register error : ret = %d\n", TCU_SRAM_DUMP_SYSFS_NAME, ret);
goto p_err;
}
npu_info("register debugfs %s\n", IDP_SRAM_DUMP_SYSFS_NAME);
ret = npu_debug_register(IDP_SRAM_DUMP_SYSFS_NAME, &NPU_SRAM_DUMP_FOPS(idp));
if (ret) {
npu_err("%s npu_debug_register error : ret = %d\n", IDP_SRAM_DUMP_SYSFS_NAME, ret);
goto p_err;
}
#endif
}
npu_info("complete in npu_util_memdump_open");
return 0;
p_err:
npu_err("fail(%d) in npu_util_memdump_open", ret);
return ret;
}
int npu_util_memdump_close(struct npu_system *system)
{
/* NOP */
return 0;
}
int npu_util_memdump_start(struct npu_system *system)
{
/* NOP */
return 0;
}
int npu_util_memdump_stop(struct npu_system *system)
{
/* NOP */
return 0;
}