381 lines
9.7 KiB
C
381 lines
9.7 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/* sound/soc/samsung/vts/vts_log.c
|
||
|
*
|
||
|
* ALSA SoC - Samsung VTS Log driver
|
||
|
*
|
||
|
* Copyright (c) 2017 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/module.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <sound/soc.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
|
||
|
#include "vts.h"
|
||
|
#include "vts_dbg.h"
|
||
|
#include "vts_log.h"
|
||
|
#include "vts_proc.h"
|
||
|
|
||
|
#define VTS_LOG_BUFFER_SIZE (SZ_1M)
|
||
|
#define S_IRWUG (0660)
|
||
|
|
||
|
struct vts_kernel_log_buffer {
|
||
|
char *buffer;
|
||
|
unsigned int index;
|
||
|
bool wrap;
|
||
|
bool updated;
|
||
|
wait_queue_head_t wq;
|
||
|
};
|
||
|
|
||
|
struct vts_log_buffer_info {
|
||
|
struct device *dev;
|
||
|
struct mutex lock;
|
||
|
struct vts_log_buffer log_buffer;
|
||
|
struct vts_kernel_log_buffer kernel_buffer;
|
||
|
bool registered;
|
||
|
u32 logbuf_index;
|
||
|
};
|
||
|
|
||
|
static struct vts_log_buffer_info glogbuf_info;
|
||
|
|
||
|
static ssize_t vts_log_file_index;
|
||
|
|
||
|
static int vts_log_file_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
struct vts_log_buffer_info *info = vts_proc_data(file);
|
||
|
struct vts_data *data = dev_get_drvdata(info->dev);
|
||
|
u32 values[3] = {0, 0, 0};
|
||
|
int result = 0;
|
||
|
|
||
|
vts_dev_info(info->dev, "%s\n", __func__);
|
||
|
|
||
|
file->private_data = info;
|
||
|
vts_log_file_index = -1;
|
||
|
|
||
|
if (data->vts_ready) {
|
||
|
values[0] = VTS_ENABLE_DEBUGLOG;
|
||
|
values[1] = 0;
|
||
|
values[2] = 0;
|
||
|
result = vts_start_ipc_transaction(
|
||
|
info->dev, data, VTS_IRQ_AP_COMMAND,
|
||
|
&values, 0, 1);
|
||
|
if (result < 0)
|
||
|
vts_dev_err(info->dev, "%s Enable_debuglog ipc transaction failed\n",
|
||
|
__func__);
|
||
|
}
|
||
|
|
||
|
data->fw_logfile_enabled = 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int vts_log_file_close(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
struct vts_log_buffer_info *info = vts_proc_data(file);
|
||
|
struct vts_data *data = dev_get_drvdata(info->dev);
|
||
|
u32 values[3] = {0, 0, 0};
|
||
|
int result = 0;
|
||
|
|
||
|
vts_dev_info(info->dev, "%s\n", __func__);
|
||
|
|
||
|
vts_log_file_index = -1;
|
||
|
|
||
|
if (data->vts_ready) {
|
||
|
values[0] = VTS_DISABLE_DEBUGLOG;
|
||
|
values[1] = 0;
|
||
|
values[2] = 0;
|
||
|
result = vts_start_ipc_transaction(
|
||
|
info->dev, data,
|
||
|
VTS_IRQ_AP_COMMAND, &values, 0, 1);
|
||
|
if (result < 0)
|
||
|
vts_dev_err(info->dev,
|
||
|
"%s Disable_debuglog ipc transaction failed\n",
|
||
|
__func__);
|
||
|
/* reset VTS SRAM debug log buffer */
|
||
|
vts_register_log_buffer(info->dev, 0, 0);
|
||
|
}
|
||
|
|
||
|
data->fw_logfile_enabled = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t vts_log_file_read(
|
||
|
struct file *file,
|
||
|
char __user *buf,
|
||
|
size_t count,
|
||
|
loff_t *ppos)
|
||
|
{
|
||
|
struct vts_log_buffer_info *info = file->private_data;
|
||
|
struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer;
|
||
|
struct device *dev = info->dev;
|
||
|
struct vts_data *vts_data = dev_get_drvdata(dev);
|
||
|
unsigned int index;
|
||
|
size_t end, size;
|
||
|
bool first = (vts_log_file_index < 0);
|
||
|
int result;
|
||
|
|
||
|
vts_dev_dbg(dev, "%s(%zu, %lld)\n", __func__, count, *ppos);
|
||
|
|
||
|
mutex_lock(&info->lock);
|
||
|
|
||
|
if (first)
|
||
|
vts_log_file_index = likely(kernel_buffer->wrap) ?
|
||
|
kernel_buffer->index : 0;
|
||
|
|
||
|
do {
|
||
|
index = kernel_buffer->index;
|
||
|
end = ((vts_log_file_index < index) ||
|
||
|
((vts_log_file_index == index)/* && !first*/)) ?
|
||
|
index : VTS_LOG_BUFFER_SIZE;
|
||
|
size = min(end - vts_log_file_index, count);
|
||
|
if (size == 0 || (first && !kernel_buffer->wrap)
|
||
|
|| (size > glogbuf_info.log_buffer.size)) {
|
||
|
mutex_unlock(&info->lock);
|
||
|
if (file->f_flags & O_NONBLOCK) {
|
||
|
vts_dev_dbg(dev, "non block\n");
|
||
|
return -EAGAIN;
|
||
|
}
|
||
|
kernel_buffer->updated = false;
|
||
|
|
||
|
result = wait_event_interruptible(kernel_buffer->wq,
|
||
|
kernel_buffer->updated);
|
||
|
|
||
|
if (result != 0) {
|
||
|
vts_dev_dbg(dev, "interrupted\n");
|
||
|
return result;
|
||
|
}
|
||
|
mutex_lock(&info->lock);
|
||
|
}
|
||
|
#ifdef VERBOSE_LOG
|
||
|
vts_dev_dbg(dev, "loop %zu, %zu, %zd, %zu\n",
|
||
|
size, end, vts_log_file_index, count);
|
||
|
#endif
|
||
|
} while (size == 0);
|
||
|
|
||
|
vts_dev_dbg(dev, "start=%zd, end=%zd size=%zd\n",
|
||
|
vts_log_file_index, end, size);
|
||
|
|
||
|
if (vts_data->running) {
|
||
|
if (copy_to_user(buf,
|
||
|
kernel_buffer->buffer + vts_log_file_index, size)) {
|
||
|
mutex_unlock(&info->lock);
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
} else {
|
||
|
vts_dev_err(dev, "%s : Wrong VTS status", __func__);
|
||
|
}
|
||
|
|
||
|
vts_log_file_index += size;
|
||
|
if (vts_log_file_index >= VTS_LOG_BUFFER_SIZE)
|
||
|
vts_log_file_index = 0;
|
||
|
|
||
|
mutex_unlock(&info->lock);
|
||
|
|
||
|
vts_dev_dbg(dev, "%s: size = %zd\n", __func__, size);
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
static unsigned int vts_log_file_poll(struct file *file, poll_table *wait)
|
||
|
{
|
||
|
struct vts_log_buffer_info *info = file->private_data;
|
||
|
struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer;
|
||
|
|
||
|
vts_dev_dbg(info->dev, "%s\n", __func__);
|
||
|
|
||
|
poll_wait(file, &kernel_buffer->wq, wait);
|
||
|
return POLLIN | POLLRDNORM;
|
||
|
}
|
||
|
|
||
|
static const struct proc_ops vts_log_fops = {
|
||
|
.proc_open = vts_log_file_open,
|
||
|
.proc_release = vts_log_file_close,
|
||
|
.proc_read = vts_log_file_read,
|
||
|
.proc_poll = vts_log_file_poll,
|
||
|
.proc_lseek = generic_file_llseek,
|
||
|
};
|
||
|
|
||
|
static void vts_log_memcpy(struct device *dev,
|
||
|
struct vts_kernel_log_buffer *kernel_buffer,
|
||
|
char *src, size_t size)
|
||
|
{
|
||
|
size_t left_size = 0;
|
||
|
struct vts_data *vts_data = dev_get_drvdata(dev);
|
||
|
|
||
|
if (!vts_data->running && size) {
|
||
|
dev_err(dev, "%s : Wrong Status & Value", __func__);
|
||
|
return;
|
||
|
}
|
||
|
#if 0
|
||
|
print_hex_dump(KERN_ERR, "vts-fw-log", DUMP_PREFIX_OFFSET, 32, 4,
|
||
|
src,
|
||
|
size, true);
|
||
|
#endif
|
||
|
|
||
|
left_size = VTS_LOG_BUFFER_SIZE - kernel_buffer->index;
|
||
|
|
||
|
vts_dev_dbg(dev, "%s(0x%x 0x%x)\n", __func__, left_size, size);
|
||
|
|
||
|
if (left_size < size) {
|
||
|
#ifdef VERBOSE_LOG
|
||
|
vts_dev_dbg(dev, "0: %s\n", src);
|
||
|
#endif
|
||
|
memcpy_fromio(kernel_buffer->buffer +
|
||
|
kernel_buffer->index, src, left_size);
|
||
|
src += left_size;
|
||
|
size -= left_size;
|
||
|
kernel_buffer->index = 0;
|
||
|
kernel_buffer->wrap = true;
|
||
|
}
|
||
|
#ifdef VERBOSE_LOG
|
||
|
vts_dev_dbg(dev, "1: %s\n", src);
|
||
|
#endif
|
||
|
memcpy_fromio(kernel_buffer->buffer + kernel_buffer->index, src, size);
|
||
|
kernel_buffer->index += (unsigned int)size;
|
||
|
}
|
||
|
|
||
|
static void vts_log_flush_work_func(struct work_struct *work)
|
||
|
{
|
||
|
struct device *dev = glogbuf_info.dev;
|
||
|
struct vts_log_buffer *log_buffer = &glogbuf_info.log_buffer;
|
||
|
struct vts_kernel_log_buffer *kernel_buffer = NULL;
|
||
|
struct vts_data *data = dev_get_drvdata(dev);
|
||
|
int logbuf_index = glogbuf_info.logbuf_index;
|
||
|
unsigned int index_writer;
|
||
|
|
||
|
kernel_buffer = &glogbuf_info.kernel_buffer;
|
||
|
|
||
|
vts_dev_dbg(dev, "%s: %d %d %d %d\n", __func__, __LINE__,
|
||
|
logbuf_index, data->running, log_buffer->size);
|
||
|
|
||
|
if (!(data->running) || !(log_buffer->size))
|
||
|
return;
|
||
|
|
||
|
log_buffer->index_writer = data->shared_info->log_pos_write;
|
||
|
log_buffer->index_reader = data->shared_info->log_pos_read;
|
||
|
index_writer = log_buffer->index_writer;
|
||
|
|
||
|
vts_dev_dbg(dev, "%s: %d 0x%x 0x%x\n",
|
||
|
__func__, __LINE__,
|
||
|
log_buffer->index_writer, log_buffer->index_reader);
|
||
|
|
||
|
if (log_buffer->index_reader == index_writer)
|
||
|
return;
|
||
|
|
||
|
if (data->fw_logfile_enabled) {
|
||
|
pm_runtime_get_sync(dev);
|
||
|
vts_start_runtime_resume(dev, 1);
|
||
|
mutex_lock(&glogbuf_info.lock);
|
||
|
|
||
|
if (log_buffer->index_reader > index_writer) {
|
||
|
vts_dev_dbg(dev, "%s1: %d 0x%x 0x%x 0x%x\n",
|
||
|
__func__, __LINE__,
|
||
|
log_buffer->index_writer,
|
||
|
log_buffer->index_reader,
|
||
|
log_buffer->size);
|
||
|
|
||
|
vts_log_memcpy(dev, kernel_buffer,
|
||
|
log_buffer->addr + log_buffer->index_reader,
|
||
|
VTS_SRAM_EVENTLOG_SIZE_MAX - log_buffer->index_reader);
|
||
|
log_buffer->index_reader = 0;
|
||
|
}
|
||
|
|
||
|
vts_dev_dbg(dev, "%s2: %d 0x%x 0x%x 0x%x\n",
|
||
|
__func__, __LINE__,
|
||
|
log_buffer->index_writer,
|
||
|
log_buffer->index_reader,
|
||
|
log_buffer->size);
|
||
|
|
||
|
vts_log_memcpy(dev, kernel_buffer,
|
||
|
log_buffer->addr + log_buffer->index_reader,
|
||
|
index_writer - log_buffer->index_reader);
|
||
|
data->shared_info->log_pos_read =
|
||
|
data->shared_info->log_pos_write;
|
||
|
log_buffer->index_reader = index_writer;
|
||
|
|
||
|
wmb(); /* memory barrier */
|
||
|
mutex_unlock(&glogbuf_info.lock);
|
||
|
kernel_buffer->updated = true;
|
||
|
pm_runtime_put_sync(dev);
|
||
|
wake_up_interruptible(&kernel_buffer->wq);
|
||
|
}
|
||
|
|
||
|
if (data->fw_logger_enabled && data->fw_log_obj) {
|
||
|
pm_runtime_get_sync(dev);
|
||
|
memlog_write(data->fw_log_obj, MEMLOG_LEVEL_INFO,
|
||
|
(log_buffer->addr+log_buffer->size*logbuf_index),
|
||
|
log_buffer->size);
|
||
|
vts_dev_dbg(dev, "fw log sync : %d",
|
||
|
memlog_sync_to_file(data->fw_log_obj));
|
||
|
pm_runtime_put_sync(dev);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static DECLARE_WORK(vts_log_work, vts_log_flush_work_func);
|
||
|
void vts_log_schedule_flush(struct device *dev, u32 index)
|
||
|
{
|
||
|
if (glogbuf_info.registered &&
|
||
|
glogbuf_info.log_buffer.size) {
|
||
|
glogbuf_info.logbuf_index = index;
|
||
|
schedule_work(&vts_log_work);
|
||
|
vts_dev_dbg(dev, "%s: VTS Log Buffer[%d] Scheduled\n",
|
||
|
__func__, index);
|
||
|
} else
|
||
|
vts_dev_warn(dev, "%s: VTS Debugging buffer not registered\n",
|
||
|
__func__);
|
||
|
}
|
||
|
EXPORT_SYMBOL(vts_log_schedule_flush);
|
||
|
|
||
|
int vts_register_log_buffer(
|
||
|
struct device *dev,
|
||
|
u32 addroffset,
|
||
|
u32 logsz)
|
||
|
{
|
||
|
struct vts_data *data = dev_get_drvdata(dev);
|
||
|
|
||
|
vts_dev_dbg(dev, "%s(offset 0x%x)\n", __func__, addroffset);
|
||
|
|
||
|
if ((addroffset + logsz) > data->sram_size) {
|
||
|
vts_dev_warn(dev, "%s: wrong offset[0x%x] or size[0x%x]\n",
|
||
|
__func__, addroffset, logsz);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!glogbuf_info.registered) {
|
||
|
glogbuf_info.dev = dev;
|
||
|
mutex_init(&glogbuf_info.lock);
|
||
|
glogbuf_info.kernel_buffer.buffer =
|
||
|
vzalloc(VTS_LOG_BUFFER_SIZE);
|
||
|
glogbuf_info.kernel_buffer.index = 0;
|
||
|
glogbuf_info.kernel_buffer.wrap = false;
|
||
|
init_waitqueue_head(&glogbuf_info.kernel_buffer.wq);
|
||
|
|
||
|
vts_proc_create_file("vts-log", 0664, NULL, &vts_log_fops, &glogbuf_info, 0);
|
||
|
glogbuf_info.registered = true;
|
||
|
}
|
||
|
|
||
|
/* Update logging buffer address and size info */
|
||
|
glogbuf_info.log_buffer.addr = data->sram_base + addroffset;
|
||
|
glogbuf_info.log_buffer.size = logsz;
|
||
|
|
||
|
vts_dev_info(dev, "%s: %d 0x%x 0x%x\n",
|
||
|
__func__, __LINE__,
|
||
|
addroffset,
|
||
|
glogbuf_info.log_buffer.size);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(vts_register_log_buffer);
|
||
|
|
||
|
void vts_log_flush(struct device *dev)
|
||
|
{
|
||
|
vts_dev_info(dev, "%s\n", __func__);
|
||
|
|
||
|
vts_log_flush_work_func(NULL);
|
||
|
}
|
||
|
EXPORT_SYMBOL(vts_log_flush);
|