| /* drivers/input/keydebug-core.c |
| * |
| * Copyright (C) 2018 Google, Inc. |
| * |
| * 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/input.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/sched.h> |
| #include <linux/sched/debug.h> |
| #include <linux/slab.h> |
| #include <linux/syscalls.h> |
| #include <linux/of.h> |
| #include <linux/time.h> |
| #include <linux/keycombo.h> |
| #include <linux/keydebug.h> |
| #include <linux/keydebug-func.h> |
| |
| /* |
| * On the kernel command line specify |
| * keydebug.kernel_top_enable=1 to enable the kernel_top |
| * By default kernel_top is turned on |
| */ |
| static int kernel_top_enable = 1; |
| module_param(kernel_top_enable, int, 0644); |
| |
| /* |
| * On the kernel command line specify |
| * keydebug.show_dstate_enable=1 to enable the show_dstate |
| * By default show_dstate is turned on |
| */ |
| static int show_dstate_enable = 1; |
| module_param(show_dstate_enable, int, 0644); |
| |
| /* |
| * On the kernel command line specify |
| * keydebug.showallcpus_enable=1 to enable the showallcpus |
| * By default showallcpus is turned on |
| */ |
| static int showallcpus_enable = 1; |
| module_param(showallcpus_enable, int, 0644); |
| |
| #define DEFAULT_DBG_DELAY 3000 /* millisecond */ |
| static int probe_cnt; |
| static struct workqueue_struct *kdbg_wq; |
| |
| void do_keydebug(struct work_struct *this) |
| { |
| struct delayed_work *dwork = container_of(this, struct delayed_work, |
| work); |
| struct keydebug_platform_data *pdata = container_of(dwork, |
| struct keydebug_platform_data, delayed_work); |
| |
| if (kernel_top_enable) |
| kernel_top_monitor(); |
| |
| if (show_dstate_enable) { |
| pr_info("======= Show D state tasks++ =======\n"); |
| show_state_filter(TASK_UNINTERRUPTIBLE); |
| pr_info("======= Show D state tasks-- =======\n"); |
| } |
| |
| if (showallcpus_enable) |
| keydebug_showallcpus(); |
| |
| pdata->keydebug_requested = false; |
| } |
| |
| static void keydebug_event(void *priv) |
| { |
| struct keydebug_platform_data *pdata = priv; |
| uint32_t msecs = DEFAULT_DBG_DELAY; |
| |
| if (pdata->keydebug_requested) { |
| pr_info("%s: request is running\n", __func__); |
| return; |
| } else { |
| if (pdata->dbg_fn_delay) |
| msecs = pdata->dbg_fn_delay; |
| |
| if (kernel_top_enable) |
| kernel_top_init(); |
| |
| queue_delayed_work(kdbg_wq, |
| &pdata->delayed_work, msecs_to_jiffies(msecs)); |
| |
| pdata->keydebug_requested = true; |
| } |
| } |
| |
| static int keydebug_parse_dt(struct device *dev, |
| struct keydebug_platform_data *pdata) |
| { |
| int ret = 0, cnt = 0, num_keys; |
| struct device_node *dt = dev_of_node(dev); |
| struct property *prop; |
| |
| /* Parse key_down_delay */ |
| if (of_property_read_u32(dt, "key_down_delay", &pdata->key_down_delay)) |
| pr_info("%s: DT:key_down_delay property not found\n", __func__); |
| |
| /* Parse dbg_delay */ |
| if (of_property_read_u32(dt, "dbg_fn_delay", &pdata->dbg_fn_delay)) |
| pr_info("%s: DT:dbg_fn_delay property not found\n", __func__); |
| |
| /* Must have keys_down property */ |
| prop = of_find_property(dt, "keys_down", NULL); |
| if (!prop) { |
| pr_err("%s: DT:keys_down property not found\n", __func__); |
| ret = -EINVAL; |
| goto err_parse_keys_down_failed; |
| } else { |
| num_keys = prop->length / sizeof(uint32_t); |
| } |
| |
| /* Allocate down_size + 1 to assure 0 terminated */ |
| pdata->keys_down = devm_kzalloc(dev, |
| (num_keys + 1) * sizeof(uint32_t), GFP_KERNEL); |
| if (!pdata->keys_down) { |
| pr_err("%s: DT:keys_down fail to allocate memory\n", __func__); |
| ret = -ENOMEM; |
| goto err_parse_keys_down_failed; |
| } |
| |
| if (of_property_read_u32_array(dt, "keys_down", |
| pdata->keys_down, num_keys)) { |
| pr_err("%s: DT:keys_down parse err\n", __func__); |
| ret = -EINVAL; |
| goto err_keys_down_failed; |
| } |
| |
| pr_info("%s: DT:key_down_delay=%d dbg_fn_delay=%d" |
| " keys_down num_keys=%d\n", __func__, pdata->key_down_delay, |
| pdata->dbg_fn_delay, num_keys); |
| |
| for (cnt = 0; cnt < num_keys; cnt++) |
| pr_info("%s: DT:keys_down=%d\n", __func__, |
| pdata->keys_down[cnt]); |
| |
| return 0; |
| |
| err_keys_down_failed: |
| devm_kfree(dev, pdata->keys_down); |
| err_parse_keys_down_failed: |
| return ret; |
| } |
| |
| static int keydebug_probe(struct platform_device *pdev) |
| { |
| int ret = -ENOMEM; |
| struct device *dev = &pdev->dev; |
| struct keycombo_platform_data *pdata_child; |
| struct keydebug_platform_data *pdata = dev_get_platdata(dev); |
| int down_size = 0, size; |
| uint32_t key, *keyp; |
| int *ckeyp; |
| |
| /* only allow one instance at a time*/ |
| if (probe_cnt) |
| return 0; |
| else |
| probe_cnt++; |
| |
| if (dev_of_node(dev) && !pdata) { |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) { |
| pr_err("%s: fail to allocate " |
| "keydebug_platform_data\n", __func__); |
| ret = -ENOMEM; |
| goto err_get_pdata_fail; |
| } |
| |
| ret = keydebug_parse_dt(dev, pdata); |
| if (ret < 0) { |
| pr_err("%s: keydebug_parse_dt fail\n", __func__); |
| goto err_parse_fail; |
| } |
| } else if (!pdata) |
| return -EINVAL; |
| |
| kdbg_wq = alloc_workqueue("kdbgd", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); |
| |
| INIT_DELAYED_WORK(&pdata->delayed_work, do_keydebug); |
| |
| pdata->pdev_child = platform_device_alloc(KEYCOMBO_NAME, |
| PLATFORM_DEVID_AUTO); |
| if (!pdata->pdev_child) |
| return -ENOMEM; |
| |
| pdata->pdev_child->dev.parent = dev; |
| |
| /* Calculate valid keys down size*/ |
| keyp = pdata->keys_down; |
| while ((key = *keyp++)) { |
| if (key >= KEY_MAX) |
| continue; |
| down_size++; |
| } |
| |
| /* Allocate down_size + 1 to assure 0 terminated */ |
| size = sizeof(struct keycombo_platform_data) |
| + sizeof(int) * (down_size + 1); |
| pdata_child = devm_kzalloc(dev, size, GFP_KERNEL); |
| if (!pdata_child) |
| goto error; |
| |
| /* Copy valid key code*/ |
| keyp = pdata->keys_down; |
| ckeyp = pdata_child->keys_down; |
| while ((*ckeyp = *keyp++)) { |
| if (*ckeyp >= KEY_MAX) |
| continue; |
| ckeyp++; |
| } |
| |
| pdata_child->priv = pdata; |
| pdata_child->key_down_fn = keydebug_event; |
| pdata_child->key_down_delay = pdata->key_down_delay; |
| ret = platform_device_add_data(pdata->pdev_child, pdata_child, size); |
| if (ret) |
| goto error; |
| |
| return platform_device_add(pdata->pdev_child); |
| error: |
| platform_device_put(pdata->pdev_child); |
| err_parse_fail: |
| devm_kfree(dev, pdata); |
| err_get_pdata_fail: |
| if (kdbg_wq) |
| destroy_workqueue(kdbg_wq); |
| probe_cnt--; |
| return ret; |
| } |
| |
| static int keydebug_remove(struct platform_device *pdev) |
| { |
| struct keydebug_platform_data *pdata = dev_get_platdata(&pdev->dev); |
| |
| if (kernel_top_enable) |
| kernel_top_exit(); |
| |
| platform_device_put(pdata->pdev_child); |
| if (kdbg_wq) |
| destroy_workqueue(kdbg_wq); |
| probe_cnt = 0; |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id keydebug_match_table[] = { |
| { .compatible = KEYDEBUG_NAME}, |
| {}, |
| }; |
| #else |
| #define keydebug_match_table NULL |
| #endif |
| |
| struct platform_driver keydebug_driver = { |
| .probe = keydebug_probe, |
| .remove = keydebug_remove, |
| .driver = { |
| .name = KEYDEBUG_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = keydebug_match_table, |
| }, |
| }; |
| |
| static int __init keydebug_init(void) |
| { |
| return platform_driver_register(&keydebug_driver); |
| } |
| |
| static void __exit keydebug_exit(void) |
| { |
| return platform_driver_unregister(&keydebug_driver); |
| } |
| |
| module_init(keydebug_init); |
| module_exit(keydebug_exit); |
| MODULE_DESCRIPTION("keydebug Driver"); |
| MODULE_LICENSE("GPL v2"); |