| /* drivers/soc/qcom/smsm_debug.c |
| * |
| * Copyright (C) 2007 Google, Inc. |
| * Copyright (c) 2009-2014, 2017, The Linux Foundation. All rights reserved. |
| * Author: Brian Swetland <swetland@google.com> |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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/debugfs.h> |
| #include <linux/list.h> |
| #include <linux/ctype.h> |
| #include <linux/jiffies.h> |
| |
| #include <soc/qcom/smem.h> |
| #include <soc/qcom/smsm.h> |
| |
| #if defined(CONFIG_DEBUG_FS) |
| |
| |
| static void debug_read_smsm_state(struct seq_file *s) |
| { |
| uint32_t *smsm; |
| int n; |
| |
| smsm = smem_find(SMEM_SMSM_SHARED_STATE, |
| SMSM_NUM_ENTRIES * sizeof(uint32_t), |
| 0, |
| SMEM_ANY_HOST_FLAG); |
| |
| if (smsm) |
| for (n = 0; n < SMSM_NUM_ENTRIES; n++) |
| seq_printf(s, "entry %d: 0x%08x\n", n, smsm[n]); |
| } |
| |
| struct SMSM_CB_DATA { |
| int cb_count; |
| void *data; |
| uint32_t old_state; |
| uint32_t new_state; |
| }; |
| static struct SMSM_CB_DATA smsm_cb_data; |
| static struct completion smsm_cb_completion; |
| |
| static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state) |
| { |
| smsm_cb_data.cb_count++; |
| smsm_cb_data.old_state = old_state; |
| smsm_cb_data.new_state = new_state; |
| smsm_cb_data.data = data; |
| complete_all(&smsm_cb_completion); |
| } |
| |
| #define UT_EQ_INT(a, b) \ |
| { \ |
| if ((a) != (b)) { \ |
| seq_printf(s, "%s:%d " #a "(%d) != " #b "(%d)\n", \ |
| __func__, __LINE__, \ |
| a, b); \ |
| break; \ |
| } \ |
| } |
| |
| #define UT_GT_INT(a, b) \ |
| { \ |
| if ((a) <= (b)) { \ |
| seq_printf(s, "%s:%d " #a "(%d) > " #b "(%d)\n", \ |
| __func__, __LINE__, \ |
| a, b); \ |
| break; \ |
| } \ |
| } |
| |
| #define SMSM_CB_TEST_INIT() \ |
| do { \ |
| smsm_cb_data.cb_count = 0; \ |
| smsm_cb_data.old_state = 0; \ |
| smsm_cb_data.new_state = 0; \ |
| smsm_cb_data.data = 0; \ |
| } while (0) |
| |
| |
| static void debug_test_smsm(struct seq_file *s) |
| { |
| int test_num = 0; |
| int ret; |
| |
| /* Test case 1 - Register new callback for notification */ |
| do { |
| test_num++; |
| SMSM_CB_TEST_INIT(); |
| ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 0); |
| |
| /* de-assert SMSM_SMD_INIT to trigger state update */ |
| UT_EQ_INT(smsm_cb_data.cb_count, 0); |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| |
| UT_EQ_INT(smsm_cb_data.cb_count, 1); |
| UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, SMSM_SMDINIT); |
| UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, 0x0); |
| UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234); |
| |
| /* re-assert SMSM_SMD_INIT to trigger state update */ |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 2); |
| UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, 0x0); |
| UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, SMSM_SMDINIT); |
| |
| /* deregister callback */ |
| ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 2); |
| |
| /* make sure state change doesn't cause any more callbacks */ |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); |
| UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 2); |
| |
| seq_printf(s, "Test %d - PASS\n", test_num); |
| } while (0); |
| |
| /* Test case 2 - Update already registered callback */ |
| do { |
| test_num++; |
| SMSM_CB_TEST_INIT(); |
| ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 0); |
| ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 1); |
| |
| /* verify both callback bits work */ |
| reinit_completion(&smsm_cb_completion); |
| UT_EQ_INT(smsm_cb_data.cb_count, 0); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 1); |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 2); |
| |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 3); |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 4); |
| |
| /* deregister 1st callback */ |
| ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 1); |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); |
| UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 4); |
| |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 5); |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 6); |
| |
| /* deregister 2nd callback */ |
| ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_INIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 2); |
| |
| /* make sure state change doesn't cause any more callbacks */ |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); |
| UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 6); |
| |
| seq_printf(s, "Test %d - PASS\n", test_num); |
| } while (0); |
| |
| /* Test case 3 - Two callback registrations with different data */ |
| do { |
| test_num++; |
| SMSM_CB_TEST_INIT(); |
| ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 0); |
| ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, |
| smsm_state_cb, (void *)0x3456); |
| UT_EQ_INT(ret, 0); |
| |
| /* verify both callbacks work */ |
| reinit_completion(&smsm_cb_completion); |
| UT_EQ_INT(smsm_cb_data.cb_count, 0); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 1); |
| UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234); |
| |
| reinit_completion(&smsm_cb_completion); |
| smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); |
| UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, |
| msecs_to_jiffies(20)), 0); |
| UT_EQ_INT(smsm_cb_data.cb_count, 2); |
| UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x3456); |
| |
| /* cleanup and unregister |
| * degregister in reverse to verify data field is |
| * being used |
| */ |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); |
| smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); |
| ret = smsm_state_cb_deregister(SMSM_APPS_STATE, |
| SMSM_INIT, |
| smsm_state_cb, (void *)0x3456); |
| UT_EQ_INT(ret, 2); |
| ret = smsm_state_cb_deregister(SMSM_APPS_STATE, |
| SMSM_SMDINIT, |
| smsm_state_cb, (void *)0x1234); |
| UT_EQ_INT(ret, 2); |
| |
| seq_printf(s, "Test %d - PASS\n", test_num); |
| } while (0); |
| } |
| |
| static void debug_read_intr_mask(struct seq_file *s) |
| { |
| uint32_t *smsm; |
| int m, n; |
| |
| smsm = smem_find(SMEM_SMSM_CPU_INTR_MASK, |
| SMSM_NUM_ENTRIES * SMSM_NUM_HOSTS * sizeof(uint32_t), |
| 0, |
| SMEM_ANY_HOST_FLAG); |
| |
| if (smsm) |
| for (m = 0; m < SMSM_NUM_ENTRIES; m++) { |
| seq_printf(s, "entry %d:", m); |
| for (n = 0; n < SMSM_NUM_HOSTS; n++) |
| seq_printf(s, " host %d: 0x%08x", |
| n, smsm[m * SMSM_NUM_HOSTS + n]); |
| seq_puts(s, "\n"); |
| } |
| } |
| |
| 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, |
| }; |
| |
| static void debug_create(const char *name, umode_t mode, |
| struct dentry *dent, |
| void (*show)(struct seq_file *)) |
| { |
| struct dentry *file; |
| |
| file = debugfs_create_file(name, mode, dent, show, &debug_ops); |
| if (!file) |
| pr_err("%s: unable to create file '%s'\n", __func__, name); |
| } |
| |
| static int __init smsm_debugfs_init(void) |
| { |
| struct dentry *dent; |
| |
| dent = debugfs_create_dir("smsm", 0); |
| if (IS_ERR(dent)) |
| return PTR_ERR(dent); |
| |
| debug_create("state", 0444, dent, debug_read_smsm_state); |
| debug_create("intr_mask", 0444, dent, debug_read_intr_mask); |
| debug_create("smsm_test", 0444, dent, debug_test_smsm); |
| |
| init_completion(&smsm_cb_completion); |
| |
| return 0; |
| } |
| |
| late_initcall(smsm_debugfs_init); |
| #endif |