| /* 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. |
| */ |
| //#define DEBUG |
| #include <linux/io.h> |
| #include <linux/debugfs.h> |
| #include <linux/mutex.h> |
| #include <linux/vmalloc.h> |
| #include <sound/soc.h> |
| |
| #include "vts.h" |
| #include "vts_log.h" |
| |
| #define VTS_LOG_BUFFER_SIZE (SZ_1M) |
| #define S_IRWUG (S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP) |
| |
| 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 dentry *vts_dbg_root_dir __read_mostly; |
| static struct vts_log_buffer_info glogbuf_info; |
| |
| struct dentry *vts_dbg_get_root_dir(void) |
| { |
| pr_debug("%s\n", __func__); |
| |
| if (vts_dbg_root_dir == NULL) |
| vts_dbg_root_dir = debugfs_create_dir("vts", NULL); |
| |
| return vts_dbg_root_dir; |
| } |
| |
| 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 = inode->i_private; |
| struct vts_data *data = dev_get_drvdata(info->dev); |
| u32 values[3] = {0, 0, 0}; |
| int result = 0; |
| |
| dev_dbg(info->dev, "%s\n", __func__); |
| |
| file->private_data = inode->i_private; |
| 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_TEST_COMMAND, &values, 0, 1); |
| if (result < 0) |
| dev_err(info->dev, "%s Enable_debuglog ipc transaction failed\n", __func__); |
| } |
| |
| data->vtslog_enabled = 1; |
| return 0; |
| } |
| |
| static int vts_log_file_close(struct inode *inode, struct file *file) |
| { |
| struct vts_log_buffer_info *info = inode->i_private; |
| struct vts_data *data = dev_get_drvdata(info->dev); |
| u32 values[3] = {0, 0, 0}; |
| int result = 0; |
| |
| dev_dbg(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_TEST_COMMAND, &values, 0, 1); |
| if (result < 0) |
| 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->vtslog_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; |
| size_t end, size; |
| bool first = (vts_log_file_index < 0); |
| int result; |
| |
| dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos); |
| |
| mutex_lock(&info->lock); |
| |
| if (vts_log_file_index < 0) |
| vts_log_file_index = likely(kernel_buffer->wrap) ? |
| kernel_buffer->index : 0; |
| |
| do { |
| end = ((vts_log_file_index < kernel_buffer->index) || |
| ((vts_log_file_index == kernel_buffer->index) |
| && !first)) ? kernel_buffer->index |
| : VTS_LOG_BUFFER_SIZE; |
| size = min(end - vts_log_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; |
| |
| result = wait_event_interruptible(kernel_buffer->wq, |
| kernel_buffer->updated); |
| |
| if (result != 0) { |
| dev_dbg(info->dev, "interrupted\n"); |
| return result; |
| } |
| mutex_lock(&info->lock); |
| } |
| #ifdef VERBOSE_LOG |
| dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end, vts_log_file_index, count); |
| #endif |
| } while (size == 0); |
| |
| dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", vts_log_file_index, end, size); |
| if (copy_to_user(buf, kernel_buffer->buffer + vts_log_file_index, size)) |
| return -EFAULT; |
| |
| vts_log_file_index += size; |
| if (vts_log_file_index >= VTS_LOG_BUFFER_SIZE) |
| vts_log_file_index = 0; |
| |
| mutex_unlock(&info->lock); |
| |
| dev_dbg(info->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; |
| |
| dev_dbg(info->dev, "%s\n", __func__); |
| |
| poll_wait(file, &kernel_buffer->wq, wait); |
| return POLLIN | POLLRDNORM; |
| } |
| |
| static const struct file_operations vts_log_fops = { |
| .open = vts_log_file_open, |
| .release = vts_log_file_close, |
| .read = vts_log_file_read, |
| .poll = vts_log_file_poll, |
| .llseek = generic_file_llseek, |
| .owner = THIS_MODULE, |
| }; |
| |
| static void vts_log_memcpy(struct device *dev, |
| char *src, size_t size) |
| { |
| struct vts_kernel_log_buffer *kernel_buffer = NULL; |
| size_t left_size = 0; |
| |
| kernel_buffer = &glogbuf_info.kernel_buffer; |
| left_size = VTS_LOG_BUFFER_SIZE - 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_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 |
| dev_dbg(dev, "1: %s\n", src); |
| #endif |
| memcpy_fromio(kernel_buffer->buffer + kernel_buffer->index, src, size); |
| kernel_buffer->index += 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; |
| int logbuf_index = glogbuf_info.logbuf_index; |
| |
| kernel_buffer = &glogbuf_info.kernel_buffer; |
| |
| dev_dbg(dev, "%s: LogBuffer Index: %d\n", __func__, |
| logbuf_index); |
| |
| mutex_lock(&glogbuf_info.lock); |
| |
| vts_log_memcpy(dev, (log_buffer->addr + |
| log_buffer->size * logbuf_index), log_buffer->size); |
| /* memory barrier */ |
| wmb(); |
| mutex_unlock(&glogbuf_info.lock); |
| |
| kernel_buffer->updated = true; |
| wake_up_interruptible(&kernel_buffer->wq); |
| } |
| |
| 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); |
| dev_dbg(dev, "%s: VTS Log Buffer[%d] Scheduled\n", |
| __func__, index); |
| } else |
| 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); |
| |
| dev_dbg(dev, "%s(offset 0x%x)\n", __func__, addroffset); |
| |
| if ((addroffset + logsz) > data->sram_size) { |
| 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); |
| |
| debugfs_create_file("vts-log", S_IRWUG, |
| vts_dbg_get_root_dir(), |
| &glogbuf_info, &vts_log_fops); |
| 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; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(vts_register_log_buffer); |
| |
| static int __init samsung_vts_log_late_initcall(void) |
| { |
| pr_info("%s\n", __func__); |
| |
| return 0; |
| } |
| late_initcall(samsung_vts_log_late_initcall); |