6db4831e98
Android 14
584 lines
14 KiB
C
584 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/arm-smccc.h> /* for Kernel Native SMC API */
|
|
#include <linux/module.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/kernel.h> /* min() */
|
|
#include <linux/uaccess.h> /* copy_to_user() */
|
|
#include <linux/sched/clock.h> /* local_clock() */
|
|
#include <linux/sched/signal.h> /* TASK_INTERRUPTIBLE/signal_pending/schedule */
|
|
#include <linux/poll.h>
|
|
#include <linux/io.h> /* ioremap() */
|
|
#include <linux/of_fdt.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of.h>
|
|
#include <linux/seq_file.h>
|
|
#include <asm/setup.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/soc/mediatek/mtk_sip_svc.h> /* for SMC ID table */
|
|
#ifdef CONFIG_OF_RESERVED_MEM
|
|
#include <linux/of_reserved_mem.h>
|
|
#define ATF_LOG_RESERVED_MEMORY_KEY "mediatek,atf-log-reserved"
|
|
#endif
|
|
|
|
/* #define ATF_LOGGER_DEBUG */
|
|
#define ATF_LOG_CTRL_BUF_SIZE 512
|
|
#define ATF_CRASH_MAGIC_NO 0xdead1abf
|
|
#define ATF_LAST_MAGIC_NO 0x41544641
|
|
|
|
static struct mutex atf_mutex; /* shared between the threads */
|
|
static struct mutex atf_raw_buf_mutex;
|
|
static u32 atf_raw_read_offset;
|
|
static wait_queue_head_t atf_log_wq;
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id atf_logger_of_ids[] = {
|
|
{ .compatible = "mediatek,atf_logger", },
|
|
{},
|
|
};
|
|
#endif
|
|
|
|
union atf_log_ctrl_t {
|
|
struct {
|
|
u64 atf_log_addr; /* 0x00 */
|
|
u64 atf_log_size;
|
|
u32 atf_write_offset;
|
|
u32 atf_read_offset;
|
|
|
|
/* must keep value after reboot */
|
|
u64 atf_crash_log_addr;
|
|
u64 atf_crash_log_size;
|
|
u32 atf_total_write_count;
|
|
u32 atf_crash_flag;
|
|
|
|
/* Remove arrays used CONFIG_NR_CPUS */
|
|
/* Please add new field before these arrays */
|
|
} info;
|
|
unsigned char data[ATF_LOG_CTRL_BUF_SIZE];
|
|
};
|
|
|
|
static union atf_log_ctrl_t *atf_buf_vir_ctrl;
|
|
static phys_addr_t atf_buf_phy_ctrl;
|
|
static phys_addr_t atf_buf_len;
|
|
static unsigned char *atf_buf_vir_addr;
|
|
static unsigned char *atf_log_vir_addr;
|
|
static unsigned int atf_log_len;
|
|
|
|
|
|
#if defined(CONFIG_OF)
|
|
static int dt_parse_atf_logger_buf(void)
|
|
{
|
|
struct device_node *atf_logger_rmem_np;
|
|
struct reserved_mem *rmem;
|
|
|
|
atf_logger_rmem_np = of_find_compatible_node(NULL, NULL,
|
|
ATF_LOG_RESERVED_MEMORY_KEY);
|
|
|
|
if (!atf_logger_rmem_np) {
|
|
pr_info("No atf_logger reserved memory\n");
|
|
return -EINVAL;
|
|
}
|
|
rmem = of_reserved_mem_lookup(atf_logger_rmem_np);
|
|
if (!rmem) {
|
|
pr_info("No rmem\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
atf_buf_phy_ctrl = (phys_addr_t) rmem->base;
|
|
atf_buf_len = (phys_addr_t) rmem->size;
|
|
|
|
#ifdef ATF_LOGGER_DEBUG
|
|
pr_notice("%s, start: %pa, size: %pa\n",
|
|
ATF_LOG_RESERVED_MEMORY_KEY,
|
|
&atf_buf_phy_ctrl, &atf_buf_len);
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static ssize_t atf_log_write(struct file *file,
|
|
const char __user *buf, size_t count, loff_t *pos)
|
|
{
|
|
unsigned long ret = -1;
|
|
unsigned long param = -1;
|
|
struct arm_smccc_res res;
|
|
|
|
if (count < 12) {
|
|
/* for coverity check */
|
|
/* use kstrtoul_from_user() instead of */
|
|
/* copy_from_user() and kstrtoul() */
|
|
ret = kstrtoul_from_user(buf, count, 16, ¶m);
|
|
}
|
|
|
|
#ifdef ATF_LOGGER_DEBUG
|
|
pr_notice("[%s]param:0x%lx, count:%zu, ret:%ld\n",
|
|
__func__, param, count, ret);
|
|
#endif
|
|
|
|
if (!ret) {
|
|
arm_smccc_smc(MTK_SIP_KERNEL_ATF_DEBUG,
|
|
param, 0, 0, 0, 0, 0, 0, &res);
|
|
} else {
|
|
wake_up_interruptible(&atf_log_wq);
|
|
}
|
|
*pos += count;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t do_read_log_to_usr(char __user *buf, size_t count)
|
|
{
|
|
size_t copy_len;
|
|
size_t right;
|
|
unsigned int local_write_index =
|
|
atf_buf_vir_ctrl->info.atf_write_offset;
|
|
unsigned int local_read_index =
|
|
atf_buf_vir_ctrl->info.atf_read_offset;
|
|
|
|
/* check copy length */
|
|
copy_len = (local_write_index +
|
|
atf_log_len - local_read_index) % atf_log_len;
|
|
|
|
/* if copy length < count, just copy the "copy length" */
|
|
if (count > copy_len)
|
|
count = copy_len;
|
|
|
|
if (local_write_index > local_read_index) {
|
|
/* write (right) - read (left) */
|
|
/* --------R-------W-----------*/
|
|
if (copy_to_user(buf,
|
|
atf_log_vir_addr + local_read_index, count))
|
|
return -EFAULT;
|
|
} else {
|
|
/* turn around to the head */
|
|
/* --------W-------R-----------*/
|
|
right = atf_log_len - local_read_index;
|
|
|
|
/* check buf space is enough to copy */
|
|
if (count > right) {
|
|
/* if space is enough to copy */
|
|
if (copy_to_user(buf,
|
|
atf_log_vir_addr + local_read_index, right))
|
|
return -EFAULT;
|
|
if (copy_to_user((buf + right),
|
|
atf_log_vir_addr, count - right))
|
|
return -EFAULT;
|
|
} else {
|
|
/* if count is only enough to copy right or count,
|
|
* just copy right or count.
|
|
*/
|
|
if (copy_to_user(buf,
|
|
atf_log_vir_addr + local_read_index, count))
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
/* update the read pos */
|
|
local_read_index = (local_read_index + count) % atf_log_len;
|
|
atf_buf_vir_ctrl->info.atf_read_offset = local_read_index;
|
|
|
|
return count;
|
|
}
|
|
|
|
static int atf_log_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = nonseekable_open(inode, file);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
file->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atf_log_release(struct inode *ignored, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t atf_log_read(struct file *file,
|
|
char __user *buf, size_t count, loff_t *f_pos)
|
|
{
|
|
ssize_t readback_bytes = 0;
|
|
int rc = 0;
|
|
unsigned int write_pos;
|
|
unsigned int read_pos;
|
|
|
|
/* inside a thread */
|
|
mutex_lock(&atf_mutex);
|
|
start:
|
|
while (1) {
|
|
/* pr_notice("atf_log_read: wait in wq\n"); */
|
|
wait_event_timeout(atf_log_wq,
|
|
(atf_buf_vir_ctrl->info.atf_write_offset !=
|
|
atf_buf_vir_ctrl->info.atf_read_offset), HZ);
|
|
|
|
write_pos = atf_buf_vir_ctrl->info.atf_write_offset;
|
|
read_pos = atf_buf_vir_ctrl->info.atf_read_offset;
|
|
|
|
if (write_pos != read_pos) {
|
|
break;
|
|
} else if (file->f_flags & O_NONBLOCK) {
|
|
rc = -EAGAIN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc) {
|
|
pr_notice("%s: rc=%d\n", __func__, rc);
|
|
/* do the work with the data you're protecting */
|
|
mutex_unlock(&atf_mutex);
|
|
return rc;
|
|
}
|
|
|
|
if (unlikely(write_pos == read_pos))
|
|
goto start;
|
|
|
|
readback_bytes = do_read_log_to_usr(buf, count);
|
|
/* update the file pos */
|
|
*f_pos += readback_bytes;
|
|
|
|
/* do the work with the data you're protecting */
|
|
mutex_unlock(&atf_mutex);
|
|
|
|
return readback_bytes;
|
|
}
|
|
|
|
static unsigned int atf_log_poll(struct file *file, poll_table *wait)
|
|
{
|
|
unsigned int ret = 0;
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
return ret;
|
|
|
|
poll_wait(file, &atf_log_wq, wait);
|
|
|
|
if (atf_buf_vir_ctrl->info.atf_write_offset !=
|
|
atf_buf_vir_ctrl->info.atf_read_offset)
|
|
ret = POLLIN | POLLRDNORM;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long atf_log_ioctl(struct file *flip,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#ifdef ATF_LOGGER_DEBUG
|
|
void show_atf_log_ctl(void)
|
|
{
|
|
pr_notice("ATF reserved memory, start: %pa, size: %pa\n",
|
|
&atf_buf_phy_ctrl, &atf_buf_len);
|
|
pr_notice("atf_buf_phy_ctrl: %pa\n", &atf_buf_phy_ctrl);
|
|
pr_notice("atf_buf_len: %pa\n", &atf_buf_len);
|
|
pr_notice("atf_buf_vir_ctrl: %pa\n", &atf_buf_vir_ctrl);
|
|
pr_notice("atf_log_vir_addr: %pa\n", &atf_log_vir_addr);
|
|
pr_notice("atf_log_len: %u\n", atf_log_len);
|
|
pr_notice("atf_log_addr(%pa) = 0x%x\n",
|
|
&(atf_buf_vir_ctrl->info.atf_log_addr),
|
|
atf_buf_vir_ctrl->info.atf_log_addr);
|
|
pr_notice("atf_log_size(%pa) = 0x%x\n",
|
|
&(atf_buf_vir_ctrl->info.atf_log_size),
|
|
atf_buf_vir_ctrl->info.atf_log_size);
|
|
pr_notice("atf_write_offset(%pa) = %u\n",
|
|
&(atf_buf_vir_ctrl->info.atf_write_offset),
|
|
atf_buf_vir_ctrl->info.atf_write_offset);
|
|
pr_notice("atf_read_offset(%pa) = %u\n",
|
|
&(atf_buf_vir_ctrl->info.atf_read_offset),
|
|
atf_buf_vir_ctrl->info.atf_read_offset);
|
|
}
|
|
|
|
static void show_data(unsigned long addr,
|
|
uint64_t nbytes, const char *name)
|
|
{
|
|
uint64_t i, j;
|
|
uint64_t nlines;
|
|
uint32_t *p = 0;
|
|
|
|
pr_notice("\n%s: %#lx:\n", name, addr);
|
|
|
|
/*
|
|
* round address down to a 32 bit boundary
|
|
* and always dump a multiple of 32 bytes
|
|
*/
|
|
p = (uint32_t *)(addr & ~(sizeof(uint32_t) - 1));
|
|
nbytes += (uint64_t) (addr & (sizeof(uint32_t) - 1));
|
|
nlines = (nbytes + 31) / 32;
|
|
|
|
for (i = 0; i < nlines; i++) {
|
|
/*
|
|
* just display low 16 bits of address to keep
|
|
* each line of the dump < 80 characters
|
|
*/
|
|
pr_notice("%#lx ", ((uintptr_t)p) & 0xffff);
|
|
for (j = 0; j < 8; j++) {
|
|
u32 data;
|
|
|
|
if (probe_kernel_address((void *)p, data))
|
|
pr_notice(" ********");
|
|
else
|
|
pr_notice(" %08x", data);
|
|
++p;
|
|
}
|
|
pr_debug("\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void atf_time_sync_resume(void)
|
|
{
|
|
/* Get local_clock and sync to ATF */
|
|
u64 time_to_sync = local_clock();
|
|
struct arm_smccc_res res;
|
|
|
|
/* always separate time_to_sync into two 32 bits args */
|
|
arm_smccc_smc(MTK_SIP_KERNEL_TIME_SYNC,
|
|
(u32)time_to_sync, (u32)(time_to_sync >> 32), 0, 0,
|
|
0, 0, 0, &res);
|
|
}
|
|
|
|
static struct syscore_ops atf_time_sync_syscore_ops = {
|
|
.resume = atf_time_sync_resume,
|
|
};
|
|
|
|
static const struct file_operations atf_log_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = atf_log_ioctl,
|
|
.compat_ioctl = atf_log_ioctl,
|
|
.poll = atf_log_poll,
|
|
.open = atf_log_open,
|
|
.release = atf_log_release,
|
|
.read = atf_log_read,
|
|
.write = atf_log_write,
|
|
};
|
|
|
|
static struct miscdevice atf_log_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "atf_log",
|
|
.fops = &atf_log_fops,
|
|
.mode = 0640,
|
|
};
|
|
|
|
static ssize_t do_read_raw_buf_to_usr(char __user *buf, size_t count)
|
|
{
|
|
size_t copy_len;
|
|
|
|
/* check copy length */
|
|
copy_len = atf_buf_len - atf_raw_read_offset;
|
|
|
|
/* if copy length < count, just copy the "copy length" */
|
|
if (count > copy_len)
|
|
count = copy_len;
|
|
|
|
if (copy_to_user(buf,
|
|
(uint8_t *)(atf_buf_vir_addr + atf_raw_read_offset), count))
|
|
return -EFAULT;
|
|
|
|
/* update the read pos */
|
|
atf_raw_read_offset += count;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t atf_raw_buf_read(struct file *file,
|
|
char __user *buf, size_t count, loff_t *f_pos)
|
|
{
|
|
ssize_t readback_bytes = 0;
|
|
|
|
/* inside a thread */
|
|
mutex_lock(&atf_raw_buf_mutex);
|
|
|
|
readback_bytes = do_read_raw_buf_to_usr(buf, count);
|
|
/* update the file pos */
|
|
*f_pos += readback_bytes;
|
|
|
|
/* do the work with the data you're protecting */
|
|
mutex_unlock(&atf_raw_buf_mutex);
|
|
|
|
return readback_bytes;
|
|
}
|
|
|
|
static int atf_raw_buf_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = nonseekable_open(inode, file);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
file->private_data = NULL;
|
|
|
|
/* clear raw buffer read offset */
|
|
mutex_lock(&atf_raw_buf_mutex);
|
|
atf_raw_read_offset = 0;
|
|
mutex_unlock(&atf_raw_buf_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int atf_raw_buf_poll(struct file *file, poll_table *wait)
|
|
{
|
|
unsigned int ret = 0;
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
return ret;
|
|
|
|
poll_wait(file, &atf_log_wq, wait);
|
|
|
|
/* always reply readable */
|
|
if (atf_raw_read_offset < atf_buf_len)
|
|
ret = POLLIN | POLLRDNORM;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations atf_raw_buf_fops = {
|
|
.owner = THIS_MODULE,
|
|
.poll = atf_raw_buf_poll,
|
|
.open = atf_raw_buf_open,
|
|
.release = atf_log_release,
|
|
.read = atf_raw_buf_read,
|
|
};
|
|
|
|
static struct miscdevice atf_raw_buf_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "atf_raw_buf",
|
|
.fops = &atf_raw_buf_fops,
|
|
.mode = 0440,
|
|
};
|
|
|
|
static int __init atf_logger_probe(struct platform_device *pdev)
|
|
{
|
|
/* register module driver */
|
|
int err;
|
|
struct proc_dir_entry *atf_log_proc_dir = NULL;
|
|
struct proc_dir_entry *atf_log_proc_file = NULL;
|
|
struct proc_dir_entry *atf_raw_buf_proc_file = NULL;
|
|
/* register module driver */
|
|
u64 time_to_sync;
|
|
struct arm_smccc_res res;
|
|
|
|
pr_notice("atf_log: inited");
|
|
err = dt_parse_atf_logger_buf();
|
|
if (unlikely(err)) {
|
|
pr_info("No atf_log_buffer\n");
|
|
return -1;
|
|
}
|
|
|
|
err = misc_register(&atf_log_dev);
|
|
if (unlikely(err)) {
|
|
pr_info("atf_log: failed to register device");
|
|
return -1;
|
|
}
|
|
|
|
err = misc_register(&atf_raw_buf_dev);
|
|
if (unlikely(err)) {
|
|
pr_info("atf_raw_buf: failed to register device");
|
|
return -1;
|
|
}
|
|
|
|
/* map whole buffer for raw dump */
|
|
atf_buf_vir_addr = ioremap_wc(atf_buf_phy_ctrl, atf_buf_len);
|
|
atf_buf_vir_ctrl = (union atf_log_ctrl_t *) atf_buf_vir_addr;
|
|
atf_log_len = atf_buf_vir_ctrl->info.atf_log_size;
|
|
atf_log_vir_addr = (unsigned char *)atf_buf_vir_ctrl +
|
|
ATF_LOG_CTRL_BUF_SIZE;
|
|
|
|
#ifdef ATF_LOGGER_DEBUG
|
|
show_atf_log_ctl();
|
|
show_data((unsigned long)atf_buf_vir_ctrl, 512, "atf_buf");
|
|
#endif
|
|
|
|
/* Synchronize timestamp in Kernel and ATF */
|
|
register_syscore_ops(&atf_time_sync_syscore_ops);
|
|
/* Get local_clock and sync to ATF */
|
|
time_to_sync = local_clock();
|
|
/* always separate time_to_sync into two 32 bits args */
|
|
arm_smccc_smc(MTK_SIP_KERNEL_TIME_SYNC,
|
|
(u32)time_to_sync, (u32)(time_to_sync >> 32), 0, 0,
|
|
0, 0, 0, &res);
|
|
|
|
/* initial wait queue */
|
|
init_waitqueue_head(&atf_log_wq);
|
|
|
|
/* create /proc/atf_log */
|
|
atf_log_proc_dir = proc_mkdir("atf_log", NULL);
|
|
if (atf_log_proc_dir == NULL) {
|
|
pr_info("atf_log proc_mkdir failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
/* create /proc/atf_log/atf_log */
|
|
atf_log_proc_file = proc_create("atf_log", 0440,
|
|
atf_log_proc_dir, &atf_log_fops);
|
|
if (atf_log_proc_file == NULL) {
|
|
pr_info("atf_log proc_create failed at atf_log\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* create /proc/atf_log/atf_raw_buf */
|
|
atf_raw_buf_proc_file = proc_create("atf_raw_buf", 0440,
|
|
atf_log_proc_dir, &atf_raw_buf_fops);
|
|
if (atf_raw_buf_proc_file == NULL) {
|
|
pr_info("atf_raw_buf proc_create failed at atf_log\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit atf_log_exit(void)
|
|
{
|
|
misc_deregister(&atf_log_dev);
|
|
pr_notice("atf_log: exited");
|
|
}
|
|
static int atf_logger_remove(struct platform_device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver atf_logger_driver_probe = {
|
|
.probe = atf_logger_probe,
|
|
.remove = atf_logger_remove,
|
|
.driver = {
|
|
.name = "atf_logger",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = atf_logger_of_ids,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static int __init atf_log_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
mutex_init(&atf_mutex); /* called only ONCE */
|
|
mutex_init(&atf_raw_buf_mutex);
|
|
|
|
ret = platform_driver_register(&atf_logger_driver_probe);
|
|
if (ret)
|
|
pr_info("atf logger init FAIL, ret 0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
module_init(atf_log_init);
|
|
module_exit(atf_log_exit);
|
|
|
|
MODULE_DESCRIPTION("MEDIATEK Module ATF Logging Driver");
|
|
MODULE_AUTHOR("Chun Fan<chun.fan@mediatek.com>");
|
|
MODULE_LICENSE("GPL");
|
|
|