/* sound/soc/samsung/abox/abox_log.c * * ALSA SoC Audio Layer - 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 #include #include #include #include "abox_util.h" #include "abox.h" #include "abox_dbg.h" #include "abox_log.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; atomic_t opened; ssize_t file_index; struct mutex lock; struct ABOX_LOG_BUFFER *log_buffer; struct abox_log_kernel_buffer kernel_buffer; }; static LIST_HEAD(abox_log_list_head); static u32 abox_log_auto_save; 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; dev_dbg(dev, "%s(%zu)\n", __func__, size); if (left_size < size) { #ifdef VERBOSE_LOG dev_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 dev_dbg(dev, "1: %s\n", src); #endif memcpy(kernel_buffer->buffer + kernel_buffer->index, src, size); kernel_buffer->index += (unsigned int)size; } static void abox_log_file_name(struct device *dev, struct abox_log_buffer_info *info, char *name, size_t size) { snprintf(name, size, "/data/calliope-%02d.log", info->id); } static void abox_log_file_save(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; char name[32]; struct file *filp; mm_segment_t old_fs; dev_dbg(dev, "%s(%d)\n", __func__, info->id); abox_log_file_name(dev, info, name, sizeof(name)); old_fs = get_fs(); set_fs(KERNEL_DS); if (likely(info->file_created)) { filp = filp_open(name, O_RDWR | O_APPEND | O_CREAT, S_IRWUG); dev_dbg(dev, "appended\n"); } else { filp = filp_open(name, O_RDWR | O_TRUNC | O_CREAT, S_IRWUG); info->file_created = true; dev_dbg(dev, "created\n"); } if (IS_ERR(filp)) { dev_warn(dev, "%s: saving log fail\n", __func__); goto out; } if (log_buffer->index_reader > index_writer) { vfs_write(filp, log_buffer->buffer + log_buffer->index_reader, log_buffer->size - log_buffer->index_reader, &filp->f_pos); vfs_write(filp, log_buffer->buffer, index_writer, &filp->f_pos); } else { vfs_write(filp, log_buffer->buffer + log_buffer->index_reader, index_writer - log_buffer->index_reader, &filp->f_pos); } vfs_fsync(filp, 0); filp_close(filp, NULL); out: set_fs(old_fs); } 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 (log_buffer->index_reader == index_writer) return; dev_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 (abox_log_auto_save) abox_log_file_save(dev, info); 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 dev_dbg(dev, "shared_buffer: %s\n", log_buffer->buffer); dev_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; dev_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); schedule_delayed_work(&abox_log_flush_all_work, msecs_to_jiffies(3000)); 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); schedule_delayed_work(&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 = inode->i_private; dev_dbg(info->dev, "%s\n", __func__); if (atomic_cmpxchg(&info->opened, 0, 1)) return -EBUSY; info->file_index = -1; file->private_data = info; return 0; } static int abox_log_file_release(struct inode *inode, struct file *file) { struct abox_log_buffer_info *info = inode->i_private; dev_dbg(info->dev, "%s\n", __func__); atomic_cmpxchg(&info->opened, 1, 0); return 0; } static ssize_t abox_log_file_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct abox_log_buffer_info *info = file->private_data; struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer; unsigned int index; size_t end, size; bool first = (info->file_index < 0); int ret; dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos); mutex_lock(&info->lock); if (first) { info->file_index = likely(kernel_buffer->wrap) ? kernel_buffer->index : 0; } do { index = kernel_buffer->index; end = ((info->file_index < index) || ((info->file_index == index) && !first)) ? index : SIZE_OF_BUFFER; size = min(end - info->file_index, count); if (size == 0) { mutex_unlock(&info->lock); if (file->f_flags & O_NONBLOCK) { dev_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) { dev_dbg(info->dev, "interrupted\n"); return ret; } mutex_lock(&info->lock); } #ifdef VERBOSE_LOG dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end, info->file_index, count); #endif } while (size == 0); dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", info->file_index, end, size); if (copy_to_user(buf, kernel_buffer->buffer + info->file_index, size)) { mutex_unlock(&info->lock); return -EFAULT; } info->file_index += size; if (info->file_index >= SIZE_OF_BUFFER) info->file_index = 0; mutex_unlock(&info->lock); dev_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_buffer_info *info = file->private_data; struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer; dev_dbg(info->dev, "%s\n", __func__); poll_wait(file, &kernel_buffer->wq, wait); return POLLIN | POLLRDNORM; } static const struct file_operations abox_log_fops = { .open = abox_log_file_open, .release = abox_log_file_release, .read = abox_log_file_read, .poll = abox_log_file_poll, .llseek = generic_file_llseek, .owner = THIS_MODULE, }; static struct abox_log_buffer_info abox_log_buffer_info_new; void abox_log_register_buffer_work_func(struct work_struct *work) { struct device *dev; int id; struct ABOX_LOG_BUFFER *buffer; struct abox_log_buffer_info *info; char name[16]; dev = abox_log_buffer_info_new.dev; id = abox_log_buffer_info_new.id; buffer = abox_log_buffer_info_new.log_buffer; abox_log_buffer_info_new.dev = NULL; abox_log_buffer_info_new.id = 0; abox_log_buffer_info_new.log_buffer = NULL; dev_info(dev, "%s(%d)\n", __func__, id); info = vmalloc(sizeof(*info)); mutex_init(&info->lock); info->id = id; info->file_created = false; atomic_set(&info->opened, 0); 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); info->dev = dev; info->log_buffer = buffer; list_add_tail(&info->list, &abox_log_list_head); snprintf(name, sizeof(name), "log-%02d", id); debugfs_create_file(name, 0664, abox_dbg_get_root_dir(), info, &abox_log_fops); } static DECLARE_WORK(abox_log_register_buffer_work, abox_log_register_buffer_work_func); int abox_log_register_buffer(struct device *dev, int id, struct ABOX_LOG_BUFFER *buffer) { struct abox_log_buffer_info *info; dev_dbg(dev, "%s(%d)\n", __func__, id); if (abox_log_buffer_info_new.dev != NULL || abox_log_buffer_info_new.id > 0 || abox_log_buffer_info_new.log_buffer != NULL) { return -EBUSY; } list_for_each_entry(info, &abox_log_list_head, list) { if (info->id == id) { dev_dbg(dev, "already registered log: %d\n", id); return 0; } } abox_log_buffer_info_new.dev = dev; abox_log_buffer_info_new.id = id; abox_log_buffer_info_new.log_buffer = buffer; schedule_work(&abox_log_register_buffer_work); 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 static int __init samsung_abox_log_late_initcall(void) { pr_info("%s\n", __func__); debugfs_create_u32("log_auto_save", S_IRWUG, abox_dbg_get_root_dir(), &abox_log_auto_save); #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; } late_initcall(samsung_abox_log_late_initcall);