| /* Copyright (c) 2016-2018, 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 <linux/module.h> |
| #include <linux/debugfs.h> |
| |
| #include "logger.h" |
| |
| #define CNSS_LOGGER_STATE_DUMP_BUFFER (2 * 1024) /* 2KB */ |
| |
| static int logger_state_dump_device(struct logger_device *dev, char *buf, |
| int buf_len) |
| { |
| int len = 0; |
| struct logger_event_handler *cur; |
| |
| len += scnprintf(buf + len, buf_len - len, |
| "==============================================\n"); |
| |
| len += scnprintf(buf + len, buf_len - len, |
| "driver [%s] is registered with radio index: %d\n", |
| dev->name, dev->radio_idx); |
| |
| if (list_empty(&dev->event_list)) { |
| len += scnprintf(buf + len, buf_len - len, |
| "No event registered\n"); |
| return len; |
| } |
| |
| list_for_each_entry(cur, &dev->event_list, list) { |
| len += scnprintf(buf + len, buf_len - len, |
| "\t event %d\n", cur->event); |
| } |
| len += scnprintf(buf + len, buf_len - len, "\n"); |
| |
| return len; |
| } |
| |
| static int logger_state_dump(struct logger_context *ctx, char *buf, int buf_len) |
| { |
| int len = 0; |
| struct logger_device *cur; |
| |
| if (list_empty(&ctx->dev_list)) { |
| len += scnprintf(buf + len, buf_len - len, |
| "=======================\n"); |
| len += scnprintf(buf + len, buf_len - len, |
| "No driver registered\n"); |
| return 0; |
| } |
| |
| list_for_each_entry(cur, &ctx->dev_list, list) |
| len += logger_state_dump_device(cur, (buf + len), buf_len); |
| |
| return 0; |
| } |
| |
| static int logger_state_open(struct inode *inode, struct file *file) |
| { |
| struct logger_context *ctx = inode->i_private; |
| void *buf; |
| int ret; |
| |
| mutex_lock(&ctx->con_mutex); |
| |
| buf = kmalloc(CNSS_LOGGER_STATE_DUMP_BUFFER, GFP_KERNEL); |
| if (!buf) { |
| ret = -ENOMEM; |
| goto error_unlock; |
| } |
| |
| ret = logger_state_dump(ctx, buf, CNSS_LOGGER_STATE_DUMP_BUFFER); |
| if (ret) |
| goto error_free; |
| |
| file->private_data = buf; |
| mutex_unlock(&ctx->con_mutex); |
| return 0; |
| |
| error_free: |
| kfree(buf); |
| |
| error_unlock: |
| mutex_unlock(&ctx->con_mutex); |
| |
| return ret; |
| } |
| |
| static int logger_state_release(struct inode *inode, struct file *file) |
| { |
| kfree(file->private_data); |
| return 0; |
| } |
| |
| static ssize_t logger_state_read(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| const char *buf = file->private_data; |
| unsigned int len = strlen(buf); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| } |
| |
| static const struct file_operations fops_logger_state = { |
| .open = logger_state_open, |
| .release = logger_state_release, |
| .read = logger_state_read, |
| .owner = THIS_MODULE, |
| .llseek = default_llseek, |
| }; |
| |
| void logger_debugfs_init(struct logger_context *ctx) |
| { |
| if (!ctx->debugfs_entry) |
| ctx->debugfs_entry = debugfs_create_dir("cnss_logger", NULL); |
| |
| debugfs_create_file("state", 0400, ctx->debugfs_entry, ctx, |
| &fops_logger_state); |
| } |
| |
| void logger_debugfs_remove(struct logger_context *ctx) |
| { |
| debugfs_remove(ctx->debugfs_entry); |
| } |
| |