| /* arch/arm/mach-msm/smp2p_debug.c |
| * |
| * Copyright (c) 2013, 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/ctype.h> |
| #include <linux/list.h> |
| #include <linux/debugfs.h> |
| #include "smp2p_private.h" |
| |
| #if defined(CONFIG_DEBUG_FS) |
| |
| /** |
| * Dump interrupt statistics. |
| * |
| * @s: pointer to output file |
| */ |
| static void smp2p_int_stats(struct seq_file *s) |
| { |
| struct smp2p_interrupt_config *int_cfg; |
| int pid; |
| |
| int_cfg = smp2p_get_interrupt_config(); |
| if (!int_cfg) |
| return; |
| |
| seq_printf(s, "| Processor | Incoming Id | Incoming # |"); |
| seq_printf(s, " Outgoing # | Base Ptr | Mask |\n"); |
| |
| for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) { |
| if (!int_cfg[pid].is_configured && |
| pid != SMP2P_REMOTE_MOCK_PROC) |
| continue; |
| |
| seq_printf(s, |
| "| %5s (%d) | %11u | %10u | %10u | %p | %08x |\n", |
| int_cfg[pid].name, |
| pid, int_cfg[pid].in_int_id, |
| int_cfg[pid].in_interrupt_count, |
| int_cfg[pid].out_interrupt_count, |
| int_cfg[pid].out_int_ptr, |
| int_cfg[pid].out_int_mask); |
| } |
| } |
| |
| /** |
| * Dump item header line 1. |
| * |
| * @buf: output buffer |
| * @max: length of output buffer |
| * @item_ptr: SMEM item pointer |
| * @state: item state |
| * @returns: Number of bytes written to output buffer |
| */ |
| static int smp2p_item_header1(char *buf, int max, struct smp2p_smem *item_ptr, |
| enum msm_smp2p_edge_state state) |
| { |
| int i = 0; |
| const char *state_text; |
| |
| if (!item_ptr) { |
| i += scnprintf(buf + i, max - i, "None"); |
| return i; |
| } |
| |
| switch (state) { |
| case SMP2P_EDGE_STATE_CLOSED: |
| state_text = "State: Closed"; |
| break; |
| case SMP2P_EDGE_STATE_OPENING: |
| state_text = "State: Opening"; |
| break; |
| case SMP2P_EDGE_STATE_OPENED: |
| state_text = "State: Opened"; |
| break; |
| default: |
| state_text = ""; |
| break; |
| } |
| |
| i += scnprintf(buf + i, max - i, |
| "%-14s LPID %d RPID %d", |
| state_text, |
| SMP2P_GET_LOCAL_PID(item_ptr->rem_loc_proc_id), |
| SMP2P_GET_REMOTE_PID(item_ptr->rem_loc_proc_id) |
| ); |
| |
| return i; |
| } |
| |
| /** |
| * Dump item header line 2. |
| * |
| * @buf: output buffer |
| * @max: length of output buffer |
| * @item_ptr: SMEM item pointer |
| * @returns: Number of bytes written to output buffer |
| */ |
| static int smp2p_item_header2(char *buf, int max, struct smp2p_smem *item_ptr) |
| { |
| int i = 0; |
| |
| if (!item_ptr) { |
| i += scnprintf(buf + i, max - i, "None"); |
| return i; |
| } |
| |
| i += scnprintf(buf + i, max - i, |
| "Version: %08x Features: %08x", |
| SMP2P_GET_VERSION(item_ptr->feature_version), |
| SMP2P_GET_FEATURES(item_ptr->feature_version) |
| ); |
| |
| return i; |
| } |
| |
| /** |
| * Dump item header line 3. |
| * |
| * @buf: output buffer |
| * @max: length of output buffer |
| * @item_ptr: SMEM item pointer |
| * @state: item state |
| * @returns: Number of bytes written to output buffer |
| */ |
| static int smp2p_item_header3(char *buf, int max, struct smp2p_smem *item_ptr) |
| { |
| int i = 0; |
| |
| if (!item_ptr) { |
| i += scnprintf(buf + i, max - i, "None"); |
| return i; |
| } |
| |
| i += scnprintf(buf + i, max - i, |
| "Entries Valid/Max: %d/%d", |
| SMP2P_GET_ENT_VALID(item_ptr->valid_total_ent), |
| SMP2P_GET_ENT_TOTAL(item_ptr->valid_total_ent) |
| ); |
| |
| return i; |
| } |
| |
| /** |
| * Dump individual input/output item pair. |
| * |
| * @s: pointer to output file |
| */ |
| static void smp2p_item(struct seq_file *s, int remote_pid) |
| { |
| struct smp2p_smem *out_ptr; |
| struct smp2p_smem *in_ptr; |
| struct smp2p_interrupt_config *int_cfg; |
| char tmp_buff[64]; |
| int state; |
| int entry; |
| struct smp2p_entry_v1 *out_entries = NULL; |
| struct smp2p_entry_v1 *in_entries = NULL; |
| int out_valid = 0; |
| int in_valid = 0; |
| |
| int_cfg = smp2p_get_interrupt_config(); |
| if (!int_cfg) |
| return; |
| if (!int_cfg[remote_pid].is_configured && |
| remote_pid != SMP2P_REMOTE_MOCK_PROC) |
| return; |
| |
| out_ptr = smp2p_get_out_item(remote_pid, &state); |
| in_ptr = smp2p_get_in_item(remote_pid); |
| |
| if (!out_ptr && !in_ptr) |
| return; |
| |
| /* print item headers */ |
| seq_printf(s, "%s%s\n", |
| " ====================================== ", |
| "======================================"); |
| scnprintf(tmp_buff, sizeof(tmp_buff), |
| "Apps(%d)->%s(%d)", |
| SMP2P_APPS_PROC, int_cfg[remote_pid].name, remote_pid); |
| seq_printf(s, "| %-37s", tmp_buff); |
| |
| scnprintf(tmp_buff, sizeof(tmp_buff), |
| "%s(%d)->Apps(%d)", |
| int_cfg[remote_pid].name, remote_pid, SMP2P_APPS_PROC); |
| seq_printf(s, "| %-37s|\n", tmp_buff); |
| seq_printf(s, "%s%s\n", |
| " ====================================== ", |
| "======================================"); |
| |
| smp2p_item_header1(tmp_buff, sizeof(tmp_buff), out_ptr, state); |
| seq_printf(s, "| %-37s", tmp_buff); |
| smp2p_item_header1(tmp_buff, sizeof(tmp_buff), in_ptr, -1); |
| seq_printf(s, "| %-37s|\n", tmp_buff); |
| |
| smp2p_item_header2(tmp_buff, sizeof(tmp_buff), out_ptr); |
| seq_printf(s, "| %-37s", tmp_buff); |
| smp2p_item_header2(tmp_buff, sizeof(tmp_buff), in_ptr); |
| seq_printf(s, "| %-37s|\n", tmp_buff); |
| |
| smp2p_item_header3(tmp_buff, sizeof(tmp_buff), out_ptr); |
| seq_printf(s, "| %-37s", tmp_buff); |
| smp2p_item_header3(tmp_buff, sizeof(tmp_buff), in_ptr); |
| seq_printf(s, "| %-37s|\n", tmp_buff); |
| |
| seq_printf(s, " %s%s\n", |
| "-------------------------------------- ", |
| "--------------------------------------"); |
| seq_printf(s, "| %-37s", |
| "Entry Name Value"); |
| seq_printf(s, "| %-37s|\n", |
| "Entry Name Value"); |
| seq_printf(s, " %s%s\n", |
| "-------------------------------------- ", |
| "--------------------------------------"); |
| |
| /* print entries */ |
| if (out_ptr) { |
| out_entries = (struct smp2p_entry_v1 *)((void *)out_ptr + |
| sizeof(struct smp2p_smem)); |
| out_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent); |
| } |
| |
| if (in_ptr) { |
| in_entries = (struct smp2p_entry_v1 *)((void *)in_ptr + |
| sizeof(struct smp2p_smem)); |
| in_valid = SMP2P_GET_ENT_VALID(out_ptr->valid_total_ent); |
| } |
| |
| for (entry = 0; out_entries || in_entries; ++entry) { |
| if (out_entries && entry < out_valid) { |
| scnprintf(tmp_buff, sizeof(tmp_buff), |
| "%-16s 0x%08x", |
| out_entries->name, |
| out_entries->entry); |
| ++out_entries; |
| } else { |
| out_entries = NULL; |
| scnprintf(tmp_buff, sizeof(tmp_buff), "None"); |
| } |
| seq_printf(s, "| %-37s", tmp_buff); |
| |
| if (in_entries && entry < in_valid) { |
| scnprintf(tmp_buff, sizeof(tmp_buff), |
| "%-16s 0x%08x", |
| in_entries->name, |
| in_entries->entry); |
| ++in_entries; |
| } else { |
| in_entries = NULL; |
| scnprintf(tmp_buff, sizeof(tmp_buff), "None"); |
| } |
| seq_printf(s, "| %-37s|\n", tmp_buff); |
| } |
| seq_printf(s, " %s%s\n\n", |
| "-------------------------------------- ", |
| "--------------------------------------"); |
| } |
| |
| /** |
| * Dump item state. |
| * |
| * @s: pointer to output file |
| */ |
| static void smp2p_items(struct seq_file *s) |
| { |
| int pid; |
| |
| for (pid = 0; pid < SMP2P_NUM_PROCS; ++pid) |
| smp2p_item(s, pid); |
| } |
| |
| static struct dentry *dent; |
| |
| static int debugfs_show(struct seq_file *s, void *data) |
| { |
| void (*show)(struct seq_file *) = s->private; |
| |
| show(s); |
| |
| return 0; |
| } |
| |
| static int debug_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, debugfs_show, inode->i_private); |
| } |
| |
| static const struct file_operations debug_ops = { |
| .open = debug_open, |
| .release = single_release, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| }; |
| |
| void debug_create(const char *name, |
| void (*show)(struct seq_file *)) |
| { |
| struct dentry *file; |
| |
| file = debugfs_create_file(name, 0444, dent, show, &debug_ops); |
| if (!file) |
| pr_err("%s: unable to create file '%s'\n", __func__, name); |
| } |
| |
| static int __init smp2p_debugfs_init(void) |
| { |
| dent = debugfs_create_dir("smp2p", 0); |
| if (IS_ERR(dent)) |
| return PTR_ERR(dent); |
| |
| debug_create("int_stats", smp2p_int_stats); |
| debug_create("items", smp2p_items); |
| |
| return 0; |
| } |
| |
| late_initcall(smp2p_debugfs_init); |
| #endif /* CONFIG_DEBUG_FS */ |