kernel_samsung_a34x-permissive/drivers/samsung/misc/sec_log.c

609 lines
15 KiB
C
Raw Normal View History

/*
* Copyright (c) 2022 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 version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/bootmem.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/memblock.h>
#include <linux/sched/clock.h>
#include <linux/sec_debug.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/vmalloc.h>
#include <linux/sec_ext.h>
#include <linux/io.h>
#include <linux/platform_device.h>
static u32 seclogger_base;
static u32 seclogger_size;
static u32 seclog_base;
static u32 seclog_size;
static u32 seclastklog_base;
static u32 seclastklog_size;
static char *sec_log_buf;
static unsigned int sec_log_size;
static unsigned int *sec_log_pos;
static unsigned int *sec_log_head;
static char *sec_logger_buf;
static unsigned int sec_logger_size;
static unsigned int *sec_logger_pos;
static sec_logger logger;
#ifdef CONFIG_SEC_DEBUG_LAST_KMSG
static char *last_kmsg_buffer;
static unsigned int last_kmsg_size;
struct reserved_mem *sec_log_get_rmem(const char *compatible)
{
struct reserved_mem *rmem;
struct device_node *rmem_np;
pr_info("%s: start to get %s\n", __func__, compatible);
rmem_np = of_find_compatible_node(NULL, NULL, compatible);
if (!rmem_np) {
pr_info("%s: no such reserved mem compatable with %s\n", __func__,
compatible);
return 0;
}
rmem = of_reserved_mem_lookup(rmem_np);
if (!rmem) {
pr_info("%s: no such reserved mem compatable with %s\n", __func__,
compatible);
return 0;
} else if (!rmem->base || !rmem->size) {
pr_info("%s: wrong base(0x%llx) or size(0x%llx)\n",
__func__, rmem->base, rmem->size);
return 0;
}
pr_info("%s: found (base=%llx, size=%llx)\n", __func__,
(unsigned long long)rmem->base, (unsigned long long)rmem->size);
return rmem;
}
EXPORT_SYMBOL(sec_log_get_rmem);
static void __init sec_log_save_old(unsigned int lastkbase, unsigned int lastksize)
{
/* provide previous log as last_kmsg */
unsigned int pos = *sec_log_pos;
last_kmsg_buffer = phys_to_virt(lastkbase);
if (last_kmsg_buffer) {
if (*sec_log_head == 1) {
last_kmsg_size = sec_log_size;
memcpy(last_kmsg_buffer, &sec_log_buf[pos], sec_log_size - pos);
memcpy(&last_kmsg_buffer[sec_log_size - pos], sec_log_buf, pos);
} else {
last_kmsg_size = pos;
memcpy(last_kmsg_buffer, sec_log_buf, lastksize);
}
pr_info("%s: saved old log at %u@%p\n",
__func__, last_kmsg_size, last_kmsg_buffer);
} else
pr_err("%s: failed saving old log %u@%p\n",
__func__, last_kmsg_size, last_kmsg_buffer);
}
static ssize_t sec_last_kmsg_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
if (pos >= last_kmsg_size)
return 0;
count = min(len, (size_t) (last_kmsg_size - pos));
if (copy_to_user(buf, last_kmsg_buffer + pos, count))
return -EFAULT;
*offset += count;
return count;
}
static const struct file_operations last_kmsg_file_ops = {
.owner = THIS_MODULE,
.read = sec_last_kmsg_read,
};
static int __init sec_last_kmsg_late_init(void)
{
struct proc_dir_entry *entry;
if (last_kmsg_buffer == NULL)
return 0;
entry = proc_create("last_kmsg", S_IFREG | 0444,
NULL, &last_kmsg_file_ops);
if (!entry) {
pr_err("%s: failed to create proc entry\n", __func__);
return 0;
}
proc_set_size(entry, last_kmsg_size);
return 0;
}
late_initcall(sec_last_kmsg_late_init);
#else
static inline void sec_log_save_old(unsigned int lastkbase, unsigned int lastksize)
{
}
#endif /* CONFIG_SEC_DEBUG_LAST_KMSG */
static ssize_t sec_log_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
if (pos >= seclog_size)
return 0;
count = min(len, (size_t) (seclog_size - pos));
if (copy_to_user(buf, sec_log_buf + pos, count))
return -EFAULT;
*offset += count;
return count;
}
static const struct file_operations sec_log_file_ops = {
.owner = THIS_MODULE,
.read = sec_log_read,
};
static int __init sec_log_init(void)
{
struct proc_dir_entry *entry;
if (sec_log_buf == NULL)
return 0;
entry = proc_create("sec_log", S_IFREG | 0444,
NULL, &sec_log_file_ops);
if (!entry) {
pr_err("%s: failed to create proc entry\n", __func__);
return 0;
}
proc_set_size(entry, seclog_size);
return 0;
}
late_initcall(sec_log_init);
static inline void sec_log_hook_logger(const char *text, size_t size)
{
unsigned int pos = 0;
/* Remove flip possibility the upper bit of the 22nd bit*/
*sec_logger_pos &= (SZ_4M - 1);
pos = *sec_logger_pos;
if (likely((unsigned int)size + pos <= sec_logger_size))
memcpy(&sec_logger_buf[pos], text, (unsigned int)size);
else {
unsigned int first = sec_logger_size - pos;
unsigned int second = (unsigned int)size - first;
memcpy(&sec_logger_buf[pos], text, first);
memcpy(&sec_logger_buf[0], text + first, second);
}
(*sec_logger_pos) += (unsigned int)size;
/* Check overflow */
if (unlikely(*sec_logger_pos >= sec_logger_size))
*sec_logger_pos -= sec_logger_size;
}
static inline void emit_sec_log(char *text, size_t size)
{
unsigned int pos = *sec_log_pos;
if (likely((unsigned int)size + pos <= sec_log_size))
memcpy(&sec_log_buf[pos], text, (unsigned int)size);
else {
unsigned int first = sec_log_size - pos;
unsigned int second = (unsigned int)size - first;
memcpy(&sec_log_buf[pos], text, first);
memcpy(&sec_log_buf[0], text + first, second);
*sec_log_head = 1;
}
(*sec_log_pos) += (unsigned int)size;
/* Check overflow */
if (unlikely(*sec_log_pos >= sec_log_size))
*sec_log_pos -= sec_log_size;
}
void register_hook_logger(void (*func)(const char *buf, size_t size))
{
logger.func_hook_logger = func;
logger.buffer = vmalloc(PAGE_SIZE * 3);
if (logger.buffer)
pr_info("sec_log: logger buffer alloc address: 0x%p\n", logger.buffer);
}
EXPORT_SYMBOL(register_hook_logger);
static int __init sec_log_buf_init(void)
{
unsigned int *sec_log_mag;
struct reserved_mem *rmem;
rmem = sec_log_get_rmem("samsung,sec-log");
seclog_base = rmem->base;
seclog_size = rmem->size;
rmem = sec_log_get_rmem("samsung,sec-lastklog");
seclastklog_base = rmem->base;
seclastklog_size = rmem->size;
sec_log_buf = persistent_ram_vmap((phys_addr_t)seclog_base, (phys_addr_t)seclog_size, 0);
sec_log_size = seclog_size - (sizeof(*sec_log_head) + sizeof(*sec_log_pos) + sizeof(*sec_log_mag));
sec_log_head = (unsigned int *)(sec_log_buf + sec_log_size);
sec_log_pos = (unsigned int *)(sec_log_buf + sec_log_size + sizeof(*sec_log_head));
sec_log_mag = (unsigned int *)(sec_log_buf + sec_log_size + sizeof(*sec_log_head) + sizeof(*sec_log_pos));
pr_info("%s: *sec_log_head:%u, *sec_log_mag:%x, *sec_log_pos:%u, sec_log_buf:0x%p, sec_log_size:0x%x\n",
__func__, *sec_log_head, *sec_log_mag, *sec_log_pos, sec_log_buf, sec_log_size);
if (*sec_log_mag != LOG_MAGIC) {
pr_info("%s: no old log found\n", __func__);
*sec_log_head = 0;
*sec_log_pos = 0;
*sec_log_mag = LOG_MAGIC;
} else {
sec_log_save_old(seclastklog_base, seclastklog_size);
}
//register_console(&sec_console);
//unregister_console(&sec_console);
register_log_text_hook(emit_sec_log);
return 0;
}
arch_initcall(sec_log_buf_init);
#ifdef CONFIG_SEC_LOG_HOOK_PMSG
static int sec_log_combine_pmsg(char *buffer, size_t count, unsigned int level)
{
char *logbuf = logger.buffer;
if (!logbuf)
return -ENOMEM;
switch(level) {
case SEC_LOGGER_LEVEL_HEADER:
{
struct tm tmBuf;
u64 tv_kernel;
unsigned int logbuf_len;
unsigned long rem_nsec;
if (logger.id == SEC_LOG_ID_EVENTS)
break;
tv_kernel = local_clock();
rem_nsec = do_div(tv_kernel, 1000000000);
time_to_tm(logger.tv_sec, 0, &tmBuf);
logbuf_len = snprintf(logbuf, SEC_LOGGER_HEADER_SIZE,
"\n[%5lu.%06lu][%d:%16s] %02d-%02d %02d:%02d:%02d.%03d %5d %5d ",
(unsigned long)tv_kernel, rem_nsec / 1000,
raw_smp_processor_id(), current->comm,
tmBuf.tm_mon + 1, tmBuf.tm_mday,
tmBuf.tm_hour, tmBuf.tm_min, tmBuf.tm_sec,
logger.tv_nsec / 1000000, logger.pid, logger.tid);
logger.func_hook_logger(logbuf, logbuf_len - 1);
}
break;
case SEC_LOGGER_LEVEL_PREFIX:
{
static const char* kPrioChars = "!.VDIWEFS";
unsigned char prio = logger.msg[0];
if (logger.id == SEC_LOG_ID_EVENTS)
break;
logbuf[0] = prio < strlen(kPrioChars) ? kPrioChars[prio] : '?';
logbuf[1] = ' ';
logger.func_hook_logger(logbuf, SEC_LOGGER_LEVEL_PREFIX);
}
break;
case SEC_LOGGER_LEVEL_TEXT:
{
char *eatnl = buffer + count - SEC_LOGGER_STRING_PAD;
if (logger.id == SEC_LOG_ID_EVENTS)
break;
else {
if (count == SEC_LOGGER_SKIP_COUNT && *eatnl != '\0')
break;
logger.func_hook_logger(buffer, count - 1);
if (count > 1 && strncmp(buffer, "!@", 2) == 0) {
/* To prevent potential buffer overrun
* put a null at the end of the buffer if required
*/
buffer[count - 1] = '\0';
pr_info("%s\n", buffer);
#ifdef CONFIG_SEC_BOOTSTAT
if (count > 5 && strncmp(buffer, "!@Boot", 6) == 0)
sec_boot_stat_add(buffer);
#endif /* CONFIG_SEC_BOOTSTAT */
}
}
}
break;
default:
break;
}
return 0;
}
int sec_log_hook_pmsg(char *buffer, size_t count)
{
sec_android_log_header_t header;
sec_pmsg_log_header_t pmsg_header;
if (!logger.buffer)
return -ENOMEM;
switch(count) {
case sizeof(pmsg_header):
memcpy((void *)&pmsg_header, buffer, count);
if (pmsg_header.magic != 'l') {
sec_log_combine_pmsg(buffer, count, SEC_LOGGER_LEVEL_TEXT);
} else {
/* save logger data */
logger.pid = pmsg_header.pid;
logger.uid = pmsg_header.uid;
logger.len = pmsg_header.len;
}
break;
case sizeof(header):
/* save logger data */
memcpy((void *)&header, buffer, count);
logger.id = header.id;
logger.tid = header.tid;
logger.tv_sec = header.tv_sec;
logger.tv_nsec = header.tv_nsec;
if (logger.id > 7) {
/* write string */
sec_log_combine_pmsg(buffer, count, SEC_LOGGER_LEVEL_TEXT);
} else {
/* write header */
sec_log_combine_pmsg(buffer, count, SEC_LOGGER_LEVEL_HEADER);
}
break;
case sizeof(unsigned char):
logger.msg[0] = buffer[0];
/* write char for prefix */
sec_log_combine_pmsg(buffer, count, SEC_LOGGER_LEVEL_PREFIX);
break;
default:
/* write string */
sec_log_combine_pmsg(buffer, count, SEC_LOGGER_LEVEL_TEXT);
break;
}
return 0;
}
EXPORT_SYMBOL(sec_log_hook_pmsg);
#endif
static int __init sec_logger_init(void)
{
unsigned int *sec_logger_mag;
struct reserved_mem *rmem;
rmem = sec_log_get_rmem("samsung,sec-logger");
seclogger_base = rmem->base;
seclogger_size = rmem->size;
#ifdef CONFIG_SEC_DEBUG_REDUCED_RMEM
if (SEC_DEBUG_LEVEL(kernel) == 0) {
memblock_free_late(seclogger_base, seclogger_size);
pr_info("freed sec-logger memory : %uMB at %#.8x\n",
(unsigned)seclogger_size / SZ_1M, (unsigned)seclogger_base);
return 0;
}
#endif
sec_logger_buf = persistent_ram_vmap((phys_addr_t)seclogger_base, (phys_addr_t)seclogger_size, 0);
sec_logger_size = seclogger_size - (sizeof(*sec_logger_pos) + sizeof(*sec_logger_mag));
sec_logger_pos = (unsigned int *)(sec_logger_buf + sec_logger_size);
sec_logger_mag = (unsigned int *)(sec_logger_buf + sec_logger_size + sizeof(*sec_logger_pos));
pr_info("%s: *sec_logger_mag:%x, *sec_logger_pos:%u, sec_logger_buf:0x%p, sec_logger_size:0x%x\n",
__func__, *sec_logger_mag, *sec_logger_pos, sec_logger_buf, sec_logger_size);
if (*sec_logger_mag != LOG_MAGIC) {
pr_info("%s: no old logger found\n", __func__);
*sec_logger_pos = 0;
*sec_logger_mag = LOG_MAGIC;
}
register_hook_logger(sec_log_hook_logger);
return 0;
}
device_initcall(sec_logger_init);
#ifdef CONFIG_SEC_AVC_LOG
static unsigned *sec_avc_log_ptr;
static char *sec_avc_log_buf;
static unsigned sec_avc_log_size;
static int __init sec_avc_log_setup(char *str)
{
unsigned size = memparse(str, &str);
unsigned long base = 0;
unsigned *sec_avc_log_mag;
if (SEC_DEBUG_LEVEL(kernel) == 0)
return 0;
/* If we encounter any problem parsing str ... */
if (!size || size != roundup_pow_of_two(size) || *str != '@'
|| kstrtoul(str + 1, 0, &base))
goto out;
if (memblock_is_region_reserved(base, size) || memblock_reserve(base, size)) {
pr_err("%s: failed reserving size %d " \
"at base 0x%lx\n", __func__, size, base);
goto out;
}
sec_avc_log_buf = persistent_ram_vmap(base, size, 0);
sec_avc_log_size = size - (sizeof(*sec_avc_log_ptr) + sizeof(*sec_avc_log_mag));
sec_avc_log_ptr = (unsigned int *)(sec_avc_log_buf + sec_avc_log_size);
sec_avc_log_mag = (unsigned int *)(sec_avc_log_buf + sec_avc_log_size + sizeof(*sec_avc_log_ptr));
pr_info("%s: *sec_avc_log_ptr:%x " \
"sec_avc_log_buf:%p sec_log_size:0x%x\n",
__func__, *sec_avc_log_ptr, sec_avc_log_buf, sec_avc_log_size);
if (*sec_avc_log_mag != LOG_MAGIC) {
pr_info("%s: no old log found\n", __func__);
*sec_avc_log_ptr = 0;
*sec_avc_log_mag = LOG_MAGIC;
}
return 1;
out:
return 0;
}
__setup("sec_avc_log=", sec_avc_log_setup);
#define BUF_SIZE 512
void sec_debug_avc_log(char *fmt, ...)
{
va_list args;
char buf[BUF_SIZE];
int len = 0;
unsigned long idx;
unsigned long size;
/* In case of sec_avc_log_setup is failed */
if (!sec_avc_log_size)
return;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
idx = *sec_avc_log_ptr;
size = strlen(buf);
if (idx + size > sec_avc_log_size - 1) {
len = scnprintf(&sec_avc_log_buf[0], size + 1, "%s\n", buf);
*sec_avc_log_ptr = len;
} else {
len = scnprintf(&sec_avc_log_buf[idx], size + 1, "%s\n", buf);
*sec_avc_log_ptr += len;
}
}
EXPORT_SYMBOL(sec_debug_avc_log);
static ssize_t sec_avc_log_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
char *page = NULL;
ssize_t ret;
int new_value;
if (!sec_avc_log_buf)
return 0;
ret = -ENOMEM;
if (count >= PAGE_SIZE)
return ret;
ret = -ENOMEM;
page = (char *)get_zeroed_page(GFP_KERNEL);
if (!page)
return ret;;
ret = -EFAULT;
if (copy_from_user(page, buf, count))
goto out;
ret = -EINVAL;
if (sscanf(page, "%u", &new_value) != 1) {
pr_info("%s\n", page);
/* print avc_log to sec_avc_log_buf */
sec_debug_avc_log("%s", page);
}
ret = count;
out:
free_page((unsigned long)page);
return ret;
}
static ssize_t sec_avc_log_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
if (sec_avc_log_buf == NULL)
return 0;
if (pos >= *sec_avc_log_ptr)
return 0;
count = min(len, (size_t)(*sec_avc_log_ptr - pos));
if (copy_to_user(buf, sec_avc_log_buf + pos, count))
return -EFAULT;
*offset += count;
return count;
}
static const struct file_operations avc_msg_file_ops = {
.owner = THIS_MODULE,
.read = sec_avc_log_read,
.write = sec_avc_log_write,
.llseek = generic_file_llseek,
};
static int __init sec_avc_log_late_init(void)
{
struct proc_dir_entry *entry;
if (sec_avc_log_buf == NULL)
return 0;
entry = proc_create("avc_msg", S_IFREG | S_IRUGO, NULL,
&avc_msg_file_ops);
if (!entry) {
pr_err("%s: failed to create proc entry\n", __func__);
return 0;
}
proc_set_size(entry, sec_avc_log_size);
return 0;
}
late_initcall(sec_avc_log_late_init);
#endif /* CONFIG_SEC_AVC_LOG */