blob: 325f16807810db39267740ecb028e9f0fb083d03 [file] [log] [blame]
/* 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");