428 lines
11 KiB
C
Executable file
428 lines
11 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA SoC - Samsung Abox Log 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.
|
|
*/
|
|
/* #define DEBUG */
|
|
#include <linux/mutex.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <sound/samsung/abox.h>
|
|
|
|
#include "abox_util.h"
|
|
#include "abox.h"
|
|
#include "abox_proc.h"
|
|
#include "abox_failsafe.h"
|
|
#include "abox_log.h"
|
|
#include "abox_memlog.h"
|
|
|
|
#undef VERBOSE_LOG
|
|
|
|
#undef TEST
|
|
#ifdef TEST
|
|
#define SIZE_OF_BUFFER (SZ_128)
|
|
#else
|
|
#define SIZE_OF_BUFFER (SZ_2M)
|
|
#endif
|
|
|
|
#define S_IRWUG (0660)
|
|
|
|
struct abox_log_kernel_buffer {
|
|
char *buffer;
|
|
unsigned int index;
|
|
bool wrap;
|
|
bool updated;
|
|
wait_queue_head_t wq;
|
|
};
|
|
|
|
struct abox_log_buffer_info {
|
|
struct list_head list;
|
|
struct device *dev;
|
|
int id;
|
|
bool file_created;
|
|
struct mutex lock;
|
|
struct ABOX_LOG_BUFFER *log_buffer;
|
|
struct abox_log_kernel_buffer kernel_buffer;
|
|
};
|
|
|
|
struct abox_log_file_info {
|
|
struct abox_log_buffer_info *info;
|
|
ssize_t index;
|
|
};
|
|
|
|
static LIST_HEAD(abox_log_list_head);
|
|
|
|
static void abox_log_memcpy(struct device *dev,
|
|
struct abox_log_kernel_buffer *kernel_buffer,
|
|
const char *src, size_t size)
|
|
{
|
|
size_t left_size = SIZE_OF_BUFFER - kernel_buffer->index;
|
|
|
|
abox_dbg(dev, "%s(%zu)\n", __func__, size);
|
|
|
|
if (left_size < size) {
|
|
#ifdef VERBOSE_LOG
|
|
abox_dbg(dev, "0: %s\n", src);
|
|
#endif
|
|
memcpy(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
|
|
abox_dbg(dev, "1: %s\n", src);
|
|
#endif
|
|
memcpy(kernel_buffer->buffer + kernel_buffer->index, src, size);
|
|
kernel_buffer->index += (unsigned int)size;
|
|
}
|
|
|
|
static bool abox_log_check_sanity(struct ABOX_LOG_BUFFER *log_buffer)
|
|
{
|
|
return (log_buffer->index_reader < ABOX_LOG_SIZE) &&
|
|
(log_buffer->index_writer < ABOX_LOG_SIZE) &&
|
|
(log_buffer->size <= ABOX_LOG_SIZE);
|
|
}
|
|
|
|
static void abox_log_flush(struct device *dev,
|
|
struct abox_log_buffer_info *info)
|
|
{
|
|
struct ABOX_LOG_BUFFER *log_buffer = info->log_buffer;
|
|
unsigned int index_writer = log_buffer->index_writer;
|
|
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
|
|
|
|
if (!abox_log_check_sanity(log_buffer)) {
|
|
dev_err_ratelimited(dev, "%d: writer=%u, reader=%u, size=%u\n",
|
|
info->id, log_buffer->index_writer,
|
|
log_buffer->index_reader, log_buffer->size);
|
|
abox_failsafe_report(dev, true);
|
|
return;
|
|
}
|
|
|
|
if (log_buffer->index_reader == index_writer)
|
|
return;
|
|
|
|
abox_dbg(dev, "%s(%d): index_writer=%u, index_reader=%u, size=%u\n",
|
|
__func__, info->id, index_writer,
|
|
log_buffer->index_reader, log_buffer->size);
|
|
|
|
mutex_lock(&info->lock);
|
|
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO)
|
|
if (info->id == 0)
|
|
abox_log_extra_copy(log_buffer->buffer,
|
|
log_buffer->index_reader, index_writer, log_buffer->size);
|
|
#endif
|
|
|
|
if (log_buffer->index_reader > index_writer) {
|
|
abox_log_memcpy(info->dev, kernel_buffer,
|
|
log_buffer->buffer + log_buffer->index_reader,
|
|
log_buffer->size - log_buffer->index_reader);
|
|
log_buffer->index_reader = 0;
|
|
}
|
|
abox_log_memcpy(info->dev, kernel_buffer,
|
|
log_buffer->buffer + log_buffer->index_reader,
|
|
index_writer - log_buffer->index_reader);
|
|
log_buffer->index_reader = index_writer;
|
|
mutex_unlock(&info->lock);
|
|
|
|
kernel_buffer->updated = true;
|
|
wake_up_interruptible(&kernel_buffer->wq);
|
|
|
|
#ifdef TEST
|
|
abox_dbg(dev, "shared_buffer: %s\n", log_buffer->buffer);
|
|
abox_dbg(dev, "kernel_buffer: %s\n", info->kernel_buffer.buffer);
|
|
#endif
|
|
}
|
|
|
|
void abox_log_flush_all(struct device *dev)
|
|
{
|
|
struct abox_log_buffer_info *info;
|
|
|
|
abox_dbg(dev, "%s\n", __func__);
|
|
|
|
list_for_each_entry(info, &abox_log_list_head, list) {
|
|
abox_log_flush(info->dev, info);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(abox_log_flush_all);
|
|
|
|
static unsigned long abox_log_flush_all_work_rearm_self;
|
|
static void abox_log_flush_all_work_func(struct work_struct *work);
|
|
static DECLARE_DEFERRABLE_WORK(abox_log_flush_all_work,
|
|
abox_log_flush_all_work_func);
|
|
|
|
static void abox_log_flush_all_work_func(struct work_struct *work)
|
|
{
|
|
abox_log_flush_all(NULL);
|
|
queue_delayed_work(system_unbound_wq, &abox_log_flush_all_work,
|
|
msecs_to_jiffies(1000));
|
|
set_bit(0, &abox_log_flush_all_work_rearm_self);
|
|
}
|
|
|
|
void abox_log_schedule_flush_all(struct device *dev)
|
|
{
|
|
if (test_and_clear_bit(0, &abox_log_flush_all_work_rearm_self))
|
|
cancel_delayed_work(&abox_log_flush_all_work);
|
|
queue_delayed_work(system_unbound_wq, &abox_log_flush_all_work,
|
|
msecs_to_jiffies(100));
|
|
}
|
|
EXPORT_SYMBOL(abox_log_schedule_flush_all);
|
|
|
|
void abox_log_drain_all(struct device *dev)
|
|
{
|
|
cancel_delayed_work(&abox_log_flush_all_work);
|
|
abox_log_flush_all(dev);
|
|
}
|
|
EXPORT_SYMBOL(abox_log_drain_all);
|
|
|
|
static int abox_log_file_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_log_buffer_info *info = abox_proc_data(file);
|
|
struct abox_log_file_info *finfo;
|
|
|
|
abox_dbg(info->dev, "%s\n", __func__);
|
|
|
|
abox_log_flush_all(info->dev);
|
|
|
|
finfo = kmalloc(sizeof(*finfo), GFP_KERNEL);
|
|
if (!finfo)
|
|
return -ENOMEM;
|
|
|
|
finfo->index = -1;
|
|
finfo->info = info;
|
|
file->private_data = finfo;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int abox_log_file_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct abox_log_file_info *finfo = file->private_data;
|
|
struct abox_log_buffer_info *info = finfo->info;
|
|
|
|
abox_dbg(info->dev, "%s\n", __func__);
|
|
kfree(finfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t abox_log_file_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct abox_log_file_info *finfo = file->private_data;
|
|
struct abox_log_buffer_info *info = finfo->info;
|
|
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
|
|
unsigned int index;
|
|
size_t end, size;
|
|
bool first = (finfo->index < 0);
|
|
int ret;
|
|
|
|
abox_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos);
|
|
|
|
mutex_lock(&info->lock);
|
|
|
|
if (first)
|
|
finfo->index = kernel_buffer->wrap ? kernel_buffer->index : 0;
|
|
|
|
do {
|
|
index = kernel_buffer->index;
|
|
end = ((finfo->index < index) ||
|
|
((finfo->index == index) && !first)) ?
|
|
index : SIZE_OF_BUFFER;
|
|
size = min(end - finfo->index, count);
|
|
if (size == 0) {
|
|
mutex_unlock(&info->lock);
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
abox_dbg(info->dev, "non block\n");
|
|
return -EAGAIN;
|
|
}
|
|
kernel_buffer->updated = false;
|
|
|
|
ret = wait_event_interruptible(kernel_buffer->wq,
|
|
kernel_buffer->updated);
|
|
if (ret != 0) {
|
|
abox_dbg(info->dev, "interrupted\n");
|
|
return ret;
|
|
}
|
|
mutex_lock(&info->lock);
|
|
}
|
|
#ifdef VERBOSE_LOG
|
|
abox_dbg(info->dev, "loop %zu, %zu, %zu, %zu\n", size, end,
|
|
finfo->index, count);
|
|
#endif
|
|
} while (size == 0);
|
|
|
|
abox_dbg(info->dev, "start=%zu, end=%zd size=%zd\n", finfo->index,
|
|
end, size);
|
|
if (copy_to_user(buf, kernel_buffer->buffer + finfo->index, size)) {
|
|
mutex_unlock(&info->lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
finfo->index += size;
|
|
if (finfo->index >= SIZE_OF_BUFFER)
|
|
finfo->index = 0;
|
|
|
|
mutex_unlock(&info->lock);
|
|
|
|
abox_dbg(info->dev, "%s: size = %zd\n", __func__, size);
|
|
|
|
return size;
|
|
}
|
|
|
|
static unsigned int abox_log_file_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct abox_log_file_info *finfo = file->private_data;
|
|
struct abox_log_buffer_info *info = finfo->info;
|
|
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
|
|
|
|
abox_dbg(info->dev, "%s\n", __func__);
|
|
|
|
poll_wait(file, &kernel_buffer->wq, wait);
|
|
return POLLIN | POLLRDNORM;
|
|
}
|
|
|
|
static const struct proc_ops abox_log_fops = {
|
|
.proc_open = abox_log_file_open,
|
|
.proc_release = abox_log_file_release,
|
|
.proc_read = abox_log_file_read,
|
|
.proc_poll = abox_log_file_poll,
|
|
.proc_lseek = generic_file_llseek,
|
|
};
|
|
|
|
static LIST_HEAD(abox_log_register_head);
|
|
static DEFINE_SPINLOCK(abox_log_register_lock);
|
|
|
|
void abox_log_register_buffer_work_func(struct work_struct *work)
|
|
{
|
|
struct abox_log_buffer_info *info, *_info, *n;
|
|
char *name;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&abox_log_register_lock, flags);
|
|
list_for_each_entry_safe(_info, n, &abox_log_register_head, list) {
|
|
if (_info->kernel_buffer.buffer)
|
|
continue;
|
|
|
|
abox_info(_info->dev, "%s(%d)\n", __func__, _info->id);
|
|
|
|
spin_unlock_irqrestore(&abox_log_register_lock, flags);
|
|
|
|
info = devm_kmemdup(_info->dev, _info, sizeof(*_info),
|
|
GFP_KERNEL);
|
|
mutex_init(&info->lock);
|
|
info->file_created = false;
|
|
info->kernel_buffer.buffer = vzalloc(SIZE_OF_BUFFER);
|
|
info->kernel_buffer.index = 0;
|
|
info->kernel_buffer.wrap = false;
|
|
init_waitqueue_head(&info->kernel_buffer.wq);
|
|
spin_lock_irqsave(&abox_log_register_lock, flags);
|
|
list_add_tail(&info->list, &abox_log_list_head);
|
|
list_del(&_info->list);
|
|
spin_unlock_irqrestore(&abox_log_register_lock, flags);
|
|
devm_kfree(_info->dev, _info);
|
|
|
|
name = kasprintf(GFP_KERNEL, "log-%02d", info->id);
|
|
abox_proc_create_file(name, 0664, NULL, &abox_log_fops,
|
|
info, 0);
|
|
kfree(name);
|
|
|
|
spin_lock_irqsave(&abox_log_register_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&abox_log_register_lock, flags);
|
|
}
|
|
|
|
static DECLARE_WORK(abox_log_register_buffer_work,
|
|
abox_log_register_buffer_work_func);
|
|
|
|
static int abox_log_init(struct device *dev)
|
|
{
|
|
static int has_run;
|
|
|
|
if (has_run)
|
|
return 0;
|
|
|
|
has_run = 1;
|
|
abox_info(dev, "%s\n", __func__);
|
|
|
|
#ifdef TEST
|
|
abox_log_test_buffer = vzalloc(SZ_128);
|
|
abox_log_test_buffer->size = SZ_64;
|
|
abox_log_register_buffer(NULL, 0, abox_log_test_buffer);
|
|
schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000));
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int abox_log_register_buffer(struct device *dev, int id,
|
|
struct ABOX_LOG_BUFFER *buffer)
|
|
{
|
|
struct abox_log_buffer_info *info;
|
|
unsigned long flags;
|
|
|
|
abox_dbg(dev, "%s(%d)\n", __func__, id);
|
|
|
|
abox_log_init(dev);
|
|
|
|
spin_lock_irqsave(&abox_log_register_lock, flags);
|
|
list_for_each_entry(info, &abox_log_list_head, list) {
|
|
if (info->id == id) {
|
|
abox_dbg(dev, "registered log: %d\n", id);
|
|
spin_unlock_irqrestore(&abox_log_register_lock, flags);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
info = devm_kzalloc(dev, sizeof(*info), GFP_ATOMIC);
|
|
if (info) {
|
|
info->dev = dev;
|
|
info->id = id;
|
|
info->log_buffer = buffer;
|
|
list_add_tail(&info->list, &abox_log_register_head);
|
|
schedule_work(&abox_log_register_buffer_work);
|
|
}
|
|
spin_unlock_irqrestore(&abox_log_register_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(abox_log_register_buffer);
|
|
|
|
#ifdef TEST
|
|
static struct ABOX_LOG_BUFFER *abox_log_test_buffer;
|
|
static void abox_log_test_work_func(struct work_struct *work);
|
|
DECLARE_DELAYED_WORK(abox_log_test_work, abox_log_test_work_func);
|
|
static void abox_log_test_work_func(struct work_struct *work)
|
|
{
|
|
struct ABOX_LOG_BUFFER *log = abox_log_test_buffer;
|
|
static unsigned int i;
|
|
char buffer[32];
|
|
char *buffer_index = buffer;
|
|
int size, left;
|
|
|
|
pr_debug("%s: %d\n", __func__, i);
|
|
|
|
size = snprintf(buffer, sizeof(buffer), "%d ", i++);
|
|
|
|
if (log->index_writer + size > log->size) {
|
|
left = log->size - log->index_writer;
|
|
memcpy(&log->buffer[log->index_writer], buffer_index, left);
|
|
log->index_writer = 0;
|
|
buffer_index += left;
|
|
}
|
|
|
|
left = size - (buffer_index - buffer);
|
|
memcpy(&log->buffer[log->index_writer], buffer_index, left);
|
|
log->index_writer += left;
|
|
|
|
abox_log_flush_all(NULL);
|
|
|
|
schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000));
|
|
}
|
|
#endif
|