/* * sec_audio_debug.c * * Copyright (c) 2018 Samsung Electronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "abox/abox.h" #define DBG_STR_BUFF_SZ 256 #define LOG_MSG_BUFF_SZ 512 #define SEC_AUDIO_DEBUG_STRING_WQ_NAME "sec_audio_dbg_str_wq" struct sec_audio_debug_data { char *dbg_str_buf; unsigned long long mode_time; unsigned long mode_nanosec_t; struct mutex dbg_lock; struct workqueue_struct *debug_string_wq; struct work_struct debug_string_work; enum abox_debug_err_type debug_err_type; unsigned int *abox_dbg_addr; }; static struct sec_audio_debug_data *p_debug_data; static struct dentry *audio_debugfs; static struct sec_audio_log_data *p_debug_log_data; static struct sec_audio_log_data *p_debug_bootlog_data; static struct sec_audio_log_data *p_debug_pmlog_data; static unsigned int debug_buff_switch; int is_abox_rdma_enabled(int id) { struct abox_data *data = abox_get_abox_data(); return (readl(data->sfr_base + ABOX_RDMA_BASE + (ABOX_RDMA_INTERVAL * id) + ABOX_RDMA_CTRL0) & ABOX_RDMA_ENABLE_MASK); } int is_abox_wdma_enabled(int id) { struct abox_data *data = abox_get_abox_data(); return (readl(data->sfr_base + ABOX_WDMA_BASE + (ABOX_WDMA_INTERVAL * id) + ABOX_WDMA_CTRL) & ABOX_WDMA_ENABLE_MASK); } static void abox_debug_string_update_workfunc(struct work_struct *wk) { struct abox_data *data = abox_get_abox_data(); int i; unsigned int len = 0; /* struct sec_audio_debug_data *dbg_data = container_of(wk, struct sec_audio_debug_data, debug_string_work); */ if (!p_debug_data) return; mutex_lock(&p_debug_data->dbg_lock); p_debug_data->mode_time = data->audio_mode_time; p_debug_data->mode_nanosec_t = do_div(p_debug_data->mode_time, NSEC_PER_SEC); p_debug_data->dbg_str_buf = kzalloc(sizeof(char) * DBG_STR_BUFF_SZ, GFP_KERNEL); if (!p_debug_data->dbg_str_buf) { pr_err("%s: no memory\n", __func__); mutex_unlock(&p_debug_data->dbg_lock); return; } switch (p_debug_data->debug_err_type) { case TYPE_ABOX_DATAABORT: case TYPE_ABOX_PREFETCHABORT: len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "ABOXERR%1d ", p_debug_data->debug_err_type); if (!abox_is_on()) { len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "Abox disabled"); goto buff_done; } if (p_debug_data->abox_dbg_addr == NULL) { len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "GPR NULL"); goto buff_done; } for (i = 0; i <= 14; i++) { len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "%08x ", *p_debug_data->abox_dbg_addr++); if (len >= DBG_STR_BUFF_SZ - 1) goto buff_done; } len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "PC:%08x ", *p_debug_data->abox_dbg_addr++); if (len >= DBG_STR_BUFF_SZ - 1) goto buff_done; len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "m%d:%05lu", data->audio_mode, (unsigned long) p_debug_data->mode_time); break; case TYPE_ABOX_OSERROR: case TYPE_ABOX_UNDEFEXCEPTION: case TYPE_ABOX_UNKNOWNERROR: len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "ABOXERR%1d ", p_debug_data->debug_err_type); if (!abox_is_on()) { len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "Abox disabled"); goto buff_done; } for (i = 0; i <= 14; i++) { len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "%08x ", readl(data->sfr_base + ABOX_CPU_R(i))); if (len >= DBG_STR_BUFF_SZ - 1) goto buff_done; } len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "PC:%08x ", readl(data->sfr_base + ABOX_CPU_PC)); if (len >= DBG_STR_BUFF_SZ - 1) goto buff_done; len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "L2C:%08x ", readl(data->sfr_base + ABOX_CPU_L2C_STATUS)); if (len >= DBG_STR_BUFF_SZ - 1) goto buff_done; len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len, "m%d:%05lu", data->audio_mode, (unsigned long) p_debug_data->mode_time); break; case TYPE_ABOX_VSSERROR: len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "VSSERROR"); break; default: pr_err("%s: unknown type %d\n", __func__, p_debug_data->debug_err_type); break; } buff_done: pr_info("%s: %s\n", __func__, p_debug_data->dbg_str_buf); sec_debug_set_extra_info_aud(p_debug_data->dbg_str_buf); kfree(p_debug_data->dbg_str_buf); p_debug_data->dbg_str_buf = NULL; mutex_unlock(&p_debug_data->dbg_lock); } void abox_debug_string_update(enum abox_debug_err_type type, void *addr) { p_debug_data->debug_err_type = type; p_debug_data->abox_dbg_addr = (unsigned int*) addr; queue_work(p_debug_data->debug_string_wq, &p_debug_data->debug_string_work); } EXPORT_SYMBOL_GPL(abox_debug_string_update); void adev_err(struct device *dev, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; va_start(args, fmt); vsnprintf(temp_buf, sizeof(temp_buf), fmt, args); va_end(args); dev_printk(KERN_ERR, dev, "%s", temp_buf); sec_audio_log(3, "%s", temp_buf); } void adev_warn(struct device *dev, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; va_start(args, fmt); vsnprintf(temp_buf, sizeof(temp_buf), fmt, args); va_end(args); dev_printk(KERN_WARNING, dev, "%s", temp_buf); sec_audio_log(4, "%s", temp_buf); } void adev_info(struct device *dev, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; va_start(args, fmt); vsnprintf(temp_buf, sizeof(temp_buf), fmt, args); va_end(args); dev_printk(KERN_INFO, dev, "%s", temp_buf); sec_audio_log(6, "%s", temp_buf); } void adev_dbg(struct device *dev, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; va_start(args, fmt); vsnprintf(temp_buf, sizeof(temp_buf), fmt, args); va_end(args); dev_printk(KERN_DEBUG, dev, "%s", temp_buf); sec_audio_log(7, "%s", temp_buf); } static int get_debug_buffer_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { ucontrol->value.integer.value[0] = debug_buff_switch; return 0; } static int set_debug_buffer_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { unsigned int val; int ret = 0; val = (unsigned int)ucontrol->value.integer.value[0]; if (val) { alloc_sec_audio_log(p_debug_log_data, SZ_1M); debug_buff_switch = SZ_1M; } else { alloc_sec_audio_log(p_debug_log_data, 0); debug_buff_switch = 0; } return ret; } static const struct snd_kcontrol_new debug_controls[] = { SOC_SINGLE_BOOL_EXT("Debug Buffer Switch", 0, get_debug_buffer_switch, set_debug_buffer_switch), }; int register_debug_mixer(struct snd_soc_card *card) { return snd_soc_add_card_controls(card, debug_controls, ARRAY_SIZE(debug_controls)); } EXPORT_SYMBOL_GPL(register_debug_mixer); static int audio_log_open_file(struct inode *inode, struct file *file) { struct sec_audio_log_data *p_dbg_log_data; if (inode->i_private) file->private_data = inode->i_private; p_dbg_log_data = file->private_data; pr_info("%s: %s\n", __func__, p_dbg_log_data->name); p_dbg_log_data->read_idx = 0; return 0; } static ssize_t audio_log_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { size_t num_msg; ssize_t ret; loff_t pos = *ppos; size_t copy_len; struct sec_audio_log_data *p_dbg_log_data = file->private_data; if (*ppos < 0 || !count) return -EINVAL; if (p_dbg_log_data->full) num_msg = p_dbg_log_data->sz_log_buff - p_dbg_log_data->read_idx; else num_msg = (size_t) p_dbg_log_data->buff_idx - p_dbg_log_data->read_idx; if (num_msg < 0) { pr_err("%s: buff idx invalid for %s\n", __func__, p_dbg_log_data->name); return -EINVAL; } if (pos > num_msg) { pr_err("%s: invalid offset for %s\n", __func__, p_dbg_log_data->name); return -EINVAL; } copy_len = min(count, num_msg); ret = copy_to_user(user_buf, p_dbg_log_data->audio_log_buffer + p_dbg_log_data->read_idx, copy_len); if (ret) { pr_err("%s: %s copy fail %d\n", __func__, p_dbg_log_data->name, (int) ret); return -EFAULT; } p_dbg_log_data->read_idx += copy_len; return copy_len; } static const struct file_operations audio_log_fops = { .open = audio_log_open_file, .read = audio_log_read_file, .llseek = default_llseek, }; static void free_sec_audio_log(struct sec_audio_log_data *p_dbg_log_data) { p_dbg_log_data->sz_log_buff = 0; if (p_dbg_log_data->virtual) vfree(p_dbg_log_data->audio_log_buffer); else kfree(p_dbg_log_data->audio_log_buffer); p_dbg_log_data->audio_log_buffer = NULL; } int alloc_sec_audio_log(struct sec_audio_log_data *p_dbg_log_data, size_t buffer_len) { if (p_dbg_log_data->sz_log_buff) { p_dbg_log_data->sz_log_buff = 0; free_sec_audio_log(p_dbg_log_data); } p_dbg_log_data->buff_idx = 0; p_dbg_log_data->full = 0; if (buffer_len <= 0) { pr_err("%s: Invalid buffer_len for %s %d\n", __func__, p_dbg_log_data->name, (int) buffer_len); p_dbg_log_data->sz_log_buff = 0; return 0; } if (p_dbg_log_data->virtual) { p_dbg_log_data->audio_log_buffer = vzalloc(buffer_len); } else { p_dbg_log_data->audio_log_buffer = kzalloc(buffer_len, GFP_KERNEL); } if (p_dbg_log_data->audio_log_buffer == NULL) { pr_err("%s: Failed to alloc audio_log_buffer for %s\n", __func__, p_dbg_log_data->name); p_dbg_log_data->sz_log_buff = 0; return -ENOMEM; } p_dbg_log_data->sz_log_buff = buffer_len; return p_dbg_log_data->sz_log_buff; } EXPORT_SYMBOL_GPL(alloc_sec_audio_log); static ssize_t log_enable_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { char buf[16]; int len; struct sec_audio_log_data *p_dbg_log_data = file->private_data; len = snprintf(buf, 16, "%d\n", (int) p_dbg_log_data->sz_log_buff); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } static ssize_t log_enable_write_file(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { char buf[16]; size_t size; u64 value; struct sec_audio_log_data *p_dbg_log_data = file->private_data; size = min(count, (sizeof(buf)-1)); if (copy_from_user(buf, user_buf, size)) { pr_err("%s: copy_from_user err\n", __func__); return -EFAULT; } buf[size] = 0; if (kstrtou64(buf, 10, &value)) { pr_err("%s: Invalid value\n", __func__); return -EINVAL; } /* do not alloc over 2MB */ if (value > SZ_2M) value = SZ_2M; alloc_sec_audio_log(p_dbg_log_data, (size_t) value); return size; } static const struct file_operations log_enable_fops = { .open = simple_open, .read = log_enable_read_file, .write = log_enable_write_file, .llseek = default_llseek, }; static ssize_t make_prefix_msg(char *buff, int level) { unsigned long long time = local_clock(); unsigned long nanosec_t = do_div(time, 1000000000); ssize_t msg_size = 0; msg_size = scnprintf(buff, LOG_MSG_BUFF_SZ, "<%d> [%5lu.%06lu] ", level, (unsigned long) time, nanosec_t / 1000); return msg_size; } static void copy_msgs(char *buff, struct sec_audio_log_data *p_dbg_log_data) { if (p_dbg_log_data->buff_idx + strlen(buff) > p_dbg_log_data->sz_log_buff - 1) { p_dbg_log_data->full = 1; p_dbg_log_data->buff_idx = 0; } p_dbg_log_data->buff_idx += scnprintf(p_dbg_log_data->audio_log_buffer + p_dbg_log_data->buff_idx, (strlen(buff) + 1), "%s", buff); } void sec_audio_log(int level, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; ssize_t temp_buff_idx = 0; struct sec_audio_log_data *p_dbg_log_data = p_debug_log_data; if (!p_dbg_log_data->sz_log_buff) { return; } temp_buff_idx = make_prefix_msg(temp_buf, level); va_start(args, fmt); temp_buff_idx += vsnprintf(temp_buf + temp_buff_idx, LOG_MSG_BUFF_SZ - temp_buff_idx, fmt, args); va_end(args); copy_msgs(temp_buf, p_dbg_log_data); } EXPORT_SYMBOL_GPL(sec_audio_log); void sec_audio_bootlog(int level, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; ssize_t temp_buff_idx = 0; struct sec_audio_log_data *p_dbg_log_data = p_debug_bootlog_data; if (!p_dbg_log_data->sz_log_buff) { return; } temp_buff_idx = make_prefix_msg(temp_buf, level); va_start(args, fmt); temp_buff_idx += vsnprintf(temp_buf + temp_buff_idx, LOG_MSG_BUFF_SZ - (temp_buff_idx + 1), fmt, args); va_end(args); copy_msgs(temp_buf, p_dbg_log_data); } EXPORT_SYMBOL_GPL(sec_audio_bootlog); void sec_audio_pmlog(int level, const char *fmt, ...) { va_list args; char temp_buf[LOG_MSG_BUFF_SZ]; ssize_t temp_buff_idx = 0; struct sec_audio_log_data *p_dbg_log_data = p_debug_pmlog_data; if (!p_dbg_log_data->sz_log_buff) { return; } temp_buff_idx = make_prefix_msg(temp_buf, level); va_start(args, fmt); temp_buff_idx += vsnprintf(temp_buf + temp_buff_idx, LOG_MSG_BUFF_SZ - (temp_buff_idx + 1), fmt, args); va_end(args); copy_msgs(temp_buf, p_dbg_log_data); } EXPORT_SYMBOL_GPL(sec_audio_pmlog); static int __init sec_audio_debug_init(void) { struct sec_audio_debug_data *data; struct sec_audio_log_data *log_data; struct sec_audio_log_data *bootlog_data; struct sec_audio_log_data *pmlog_data; int ret = 0; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) { pr_err("%s: failed to alloc data\n", __func__); return -ENOMEM; } p_debug_data = data; mutex_init(&p_debug_data->dbg_lock); audio_debugfs = debugfs_create_dir("audio", NULL); if (!audio_debugfs) { pr_err("Failed to create audio debugfs\n"); ret = -EPERM; goto err_data; } log_data = kzalloc(sizeof(*log_data), GFP_KERNEL); if (!log_data) { pr_err("%s: failed to alloc log_data\n", __func__); ret = -ENOMEM; goto err_debugfs; } p_debug_log_data = log_data; /* p_debug_log_data->audio_log_buffer = NULL; p_debug_log_data->buff_idx = 0; p_debug_log_data->full = 0; p_debug_log_data->read_idx = 0; p_debug_log_data->sz_log_buff = 0; */ p_debug_log_data->virtual = 1; p_debug_log_data->name = kasprintf(GFP_KERNEL, "runtime"); bootlog_data = kzalloc(sizeof(*bootlog_data), GFP_KERNEL); if (!bootlog_data) { pr_err("%s: failed to alloc bootlog_data\n", __func__); ret = -ENOMEM; goto err_log_data; } p_debug_bootlog_data = bootlog_data; p_debug_bootlog_data->name = kasprintf(GFP_KERNEL, "boot"); pmlog_data = kzalloc(sizeof(*pmlog_data), GFP_KERNEL); if (!pmlog_data) { pr_err("%s: failed to alloc pmlog_data\n", __func__); ret = -ENOMEM; goto err_bootlog_data; } p_debug_pmlog_data = pmlog_data; p_debug_pmlog_data->name = kasprintf(GFP_KERNEL, "pm"); alloc_sec_audio_log(p_debug_bootlog_data, SZ_1K); alloc_sec_audio_log(p_debug_pmlog_data, SZ_4K); p_debug_data->debug_string_wq = create_singlethread_workqueue( SEC_AUDIO_DEBUG_STRING_WQ_NAME); if (p_debug_data->debug_string_wq == NULL) { pr_err("%s: failed to creat debug_string_wq\n", __func__); ret = -ENOENT; goto err_pmlog_data; } INIT_WORK(&p_debug_data->debug_string_work, abox_debug_string_update_workfunc); debugfs_create_file("log_enable", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_log_data, &log_enable_fops); debugfs_create_file("log", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_log_data, &audio_log_fops); debugfs_create_file("bootlog_enable", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_bootlog_data, &log_enable_fops); debugfs_create_file("bootlog", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_bootlog_data, &audio_log_fops); debugfs_create_file("pmlog_enable", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_pmlog_data, &log_enable_fops); debugfs_create_file("pmlog", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, audio_debugfs, p_debug_pmlog_data, &audio_log_fops); return 0; err_pmlog_data: kfree_const(p_debug_pmlog_data->name); kfree(p_debug_pmlog_data); p_debug_pmlog_data = NULL; err_bootlog_data: kfree_const(p_debug_bootlog_data->name); kfree(p_debug_bootlog_data); p_debug_bootlog_data = NULL; err_log_data: kfree_const(p_debug_log_data->name); kfree(p_debug_log_data); p_debug_log_data = NULL; err_debugfs: debugfs_remove_recursive(audio_debugfs); err_data: kfree(p_debug_data); p_debug_data = NULL; return ret; } early_initcall(sec_audio_debug_init); static void __exit sec_audio_debug_exit(void) { if (p_debug_pmlog_data->sz_log_buff) free_sec_audio_log(p_debug_pmlog_data); kfree_const(p_debug_pmlog_data->name); if (p_debug_pmlog_data) kfree(p_debug_pmlog_data); p_debug_pmlog_data = NULL; if (p_debug_bootlog_data->sz_log_buff) free_sec_audio_log(p_debug_bootlog_data); kfree_const(p_debug_bootlog_data->name); if (p_debug_bootlog_data) kfree(p_debug_bootlog_data); p_debug_bootlog_data = NULL; if (p_debug_log_data->sz_log_buff) free_sec_audio_log(p_debug_log_data); kfree_const(p_debug_log_data->name); if (p_debug_log_data) kfree(p_debug_log_data); p_debug_log_data = NULL; destroy_workqueue(p_debug_data->debug_string_wq); p_debug_data->debug_string_wq = NULL; debugfs_remove_recursive(audio_debugfs); kfree(p_debug_data); p_debug_data = NULL; } module_exit(sec_audio_debug_exit); MODULE_DESCRIPTION("Samsung Electronics Audio Debug driver"); MODULE_LICENSE("GPL");