/* Copyright (c) 2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipc_logging.h" static DEFINE_MUTEX(ipc_log_debugfs_init_lock); static struct dentry *root_dent; #define MAX_MSG_DECODED_SIZE (MAX_MSG_SIZE*4) static void *get_deserialization_func(struct ipc_log_context *ilctxt, int type) { struct dfunc_info *df_info = NULL; if (!ilctxt) return NULL; list_for_each_entry(df_info, &ilctxt->dfunc_info_list, list) { if (df_info->type == type) return df_info->dfunc; } return NULL; } static int deserialize_log(struct ipc_log_context *ilctxt, char *buff, int size) { struct encode_context ectxt; struct decode_context dctxt; void (*deserialize_func)(struct encode_context *ectxt, struct decode_context *dctxt); unsigned long flags; dctxt.output_format = OUTPUT_DEBUGFS; dctxt.buff = buff; dctxt.size = size; spin_lock_irqsave(&ipc_log_context_list_lock, flags); spin_lock(&ilctxt->ipc_log_context_lock); while (dctxt.size >= MAX_MSG_DECODED_SIZE && !is_ilctxt_empty(ilctxt)) { msg_read(ilctxt, &ectxt); deserialize_func = get_deserialization_func(ilctxt, ectxt.hdr.type); spin_unlock(&ilctxt->ipc_log_context_lock); spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); if (deserialize_func) deserialize_func(&ectxt, &dctxt); else pr_err("%s: unknown message 0x%x\n", __func__, ectxt.hdr.type); spin_lock_irqsave(&ipc_log_context_list_lock, flags); spin_lock(&ilctxt->ipc_log_context_lock); } if ((size - dctxt.size) == 0) init_completion(&ilctxt->read_avail); spin_unlock(&ilctxt->ipc_log_context_lock); spin_unlock_irqrestore(&ipc_log_context_list_lock, flags); return size - dctxt.size; } static int debug_log(struct ipc_log_context *ilctxt, char *buff, int size, int cont) { int i = 0; if (size < MAX_MSG_DECODED_SIZE) { pr_err("%s: buffer size %d < %d\n", __func__, size, MAX_MSG_DECODED_SIZE); return -ENOMEM; } do { i = deserialize_log(ilctxt, buff, size - 1); if (cont && i == 0) { wait_for_completion_interruptible(&ilctxt->read_avail); if (signal_pending(current)) break; } } while (cont && i == 0); return i; } /* * VFS Read operation helper which dispatches the call to the debugfs * read command stored in file->private_data. * * @file File structure * @buff user buffer * @count size of user buffer * @ppos file position to read from (only a value of 0 is accepted) * @cont 1 = continuous mode (don't return 0 to signal end-of-file) * * @returns ==0 end of file * >0 number of bytes read * <0 error */ static ssize_t debug_read_helper(struct file *file, char __user *buff, size_t count, loff_t *ppos, int cont) { struct ipc_log_context *ilctxt = file->private_data; char *buffer; int bsize; buffer = kmalloc(count, GFP_KERNEL); if (!buffer) return -ENOMEM; bsize = debug_log(ilctxt, buffer, count, cont); if (bsize > 0) { if (copy_to_user(buff, buffer, bsize)) { kfree(buffer); return -EFAULT; } *ppos += bsize; } kfree(buffer); return bsize; } static ssize_t debug_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { return debug_read_helper(file, buff, count, ppos, 0); } static ssize_t debug_read_cont(struct file *file, char __user *buff, size_t count, loff_t *ppos) { return debug_read_helper(file, buff, count, ppos, 1); } static int debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static const struct file_operations debug_ops = { .read = debug_read, .open = debug_open, }; static const struct file_operations debug_ops_cont = { .read = debug_read_cont, .open = debug_open, }; static void debug_create(const char *name, mode_t mode, struct dentry *dent, struct ipc_log_context *ilctxt, const struct file_operations *fops) { debugfs_create_file(name, mode, dent, ilctxt, fops); } static void dfunc_string(struct encode_context *ectxt, struct decode_context *dctxt) { tsv_timestamp_read(ectxt, dctxt, " "); tsv_byte_array_read(ectxt, dctxt, ""); /* add trailing \n if necessary */ if (*(dctxt->buff - 1) != '\n') { if (dctxt->size) { ++dctxt->buff; --dctxt->size; } *(dctxt->buff - 1) = '\n'; } } void check_and_create_debugfs(void) { mutex_lock(&ipc_log_debugfs_init_lock); if (!root_dent) { root_dent = debugfs_create_dir("ipc_logging", 0); if (IS_ERR(root_dent)) { pr_err("%s: unable to create debugfs %ld\n", __func__, IS_ERR(root_dent)); root_dent = NULL; } } mutex_unlock(&ipc_log_debugfs_init_lock); } EXPORT_SYMBOL(check_and_create_debugfs); void create_ctx_debugfs(struct ipc_log_context *ctxt, const char *mod_name) { if (!root_dent) check_and_create_debugfs(); if (root_dent) { ctxt->dent = debugfs_create_dir(mod_name, root_dent); if (!IS_ERR(ctxt->dent)) { debug_create("log", 0444, ctxt->dent, ctxt, &debug_ops); debug_create("log_cont", 0444, ctxt->dent, ctxt, &debug_ops_cont); } } add_deserialization_func((void *)ctxt, TSV_TYPE_STRING, dfunc_string); } EXPORT_SYMBOL(create_ctx_debugfs);