blob: 526eb48e434decbf69b13b40f83ef985bcc92da4 [file] [log] [blame]
/*
* Copyright (c) 2019, 2021 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_wakeup.h>
#include <linux/rwsem.h>
#include <linux/suspend.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/debugfs.h>
#include "../../remoteproc/remoteproc_internal.h"
/*
* To call rproc boot and shutdown of WCSS alone,
* 1) insmod testssr.ko (test_id by default is set to 1)
* 2) rmmod testssr
* In case if multipd architectire used in WCSS/QDSP6
* 1)insmod testssr.ko test_id=1 mpd=1
* 2)rmmod testssr.ko
*/
static struct notifier_block nb;
static struct notifier_block atomic_nb;
#define WCSS_RPROC "cd00000.remoteproc"
static int test_id = 1;
module_param(test_id, int, S_IRUGO | S_IWUSR | S_IWGRP);
static ulong mpd;
module_param(mpd, ulong, S_IRUGO | S_IWUSR | S_IWGRP);
#define MAX_USERPD_CNT 3
struct userpd_info {
struct rproc *rproc;
/*0-stop, 1-start*/
bool pd_curr_state;
};
struct userpd_dbgfs_info {
struct dentry *userpd;
/*0-stop, 1-start*/
struct dentry *start_stop;
struct userpd_info userpd_hdl;
};
static struct dentry *q6_userpd_debug;
static struct userpd_dbgfs_info *userpd_dbgfs_hdl[MAX_USERPD_CNT];
static const char *action_to_string(enum subsys_notif_type action)
{
switch (action) {
case SUBSYS_BEFORE_SHUTDOWN:
return __stringify(SUBSYS_BEFORE_SHUTDOWN);
case SUBSYS_AFTER_SHUTDOWN:
return __stringify(SUBSYS_AFTER_SHUTDOWN);
case SUBSYS_BEFORE_POWERUP:
return __stringify(SUBSYS_BEFORE_POWERUP);
case SUBSYS_AFTER_POWERUP:
return __stringify(SUBSYS_AFTER_POWERUP);
case SUBSYS_RAMDUMP_NOTIFICATION:
return __stringify(SUBSYS_RAMDUMP_NOTIFICATION);
case SUBSYS_POWERUP_FAILURE:
return __stringify(SUBSYS_POWERUP_FAILURE);
case SUBSYS_PROXY_VOTE:
return __stringify(SUBSYS_PROXY_VOTE);
case SUBSYS_PROXY_UNVOTE:
return __stringify(SUBSYS_PROXY_UNVOTE);
case SUBSYS_SOC_RESET:
return __stringify(SUBSYS_SOC_RESET);
case SUBSYS_PREPARE_FOR_FATAL_SHUTDOWN:
return __stringify(SUBSYS_PREPARE_FOR_FATAL_SHUTDOWN);
default:
return "unknown";
}
}
static int tssr_notifier(struct notifier_block *nb, unsigned long action,
void *data)
{
struct notif_data *notif = (struct notif_data *)data;
pr_emerg("%s:\tReceived [%s] notification from [%s]subsystem\n",
THIS_MODULE->name, action_to_string(action), notif->pdev->name);
return NOTIFY_DONE;
}
static int test_rproc_notif_register(const char *rproc_name)
{
int ret;
ret = rproc_register_subsys_notifier(rproc_name, &nb, &atomic_nb);
if (ret)
pr_emerg("rproc notif reg failed\n");
return ret;
}
#if defined(CONFIG_QCOM_Q6V5_WCSS)
static struct rproc *get_rproc_from_phandle(void)
{
struct device_node *of_np;
phandle rproc_phandle;
struct rproc *q6rproc;
of_np = of_find_node_with_property(NULL, "qcom,rproc");
if (!of_np) {
pr_err("no node with qcom,rproc NULLi\n");
return NULL;
}
if (of_property_read_u32(of_np, "qcom,rproc", &rproc_phandle)) {
pr_err("could not get rproc phandle\n");
return NULL;
}
q6rproc = rproc_get_by_phandle(rproc_phandle);
if (!q6rproc) {
pr_err("could not get the rproc handle\n");
return NULL;
}
return q6rproc;
}
#endif
static ssize_t show_start_stop(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct userpd_dbgfs_info *hdl = file->private_data;
char _buf[16] = {0};
int ret;
snprintf(_buf, sizeof(_buf), "%d\n", hdl->userpd_hdl.pd_curr_state);
ret = simple_read_from_buffer(user_buf, count, ppos, _buf,
strnlen(_buf, 16));
return ret;
}
static ssize_t store_start_stop(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct userpd_dbgfs_info *hdl = file->private_data;
bool state;
int ret;
ret = kstrtobool_from_user(user_buf, count, &state);
if (ret) {
pr_err("Failed to retrieve userbuf value\n");
return ret;
}
if (state == hdl->userpd_hdl.pd_curr_state)
return -EINVAL;
if (state) {
ret = rproc_boot(hdl->userpd_hdl.rproc);
if (ret) {
pr_err("couldn't boot q6v5: %d\n", ret);
return ret;
}
/*userpd started*/
hdl->userpd_hdl.pd_curr_state = true;
} else {
rproc_shutdown(hdl->userpd_hdl.rproc);
/*userpd stopped*/
hdl->userpd_hdl.pd_curr_state = false;
}
return count;
}
static const struct file_operations userpd_ops = {
.open = simple_open,
.write = store_start_stop,
.read = show_start_stop,
};
static int get_userpd_cnt(struct rproc *q6rproc)
{
struct device_node *upd_np;
int cnt = 0;
for_each_available_child_of_node(q6rproc->dev.parent->of_node, upd_np)
cnt += strstr(upd_np->name, "pd") ? 1 : 0;
return cnt;
}
struct rproc *get_userpd_rproc(struct rproc *q6rproc, int pd_asid)
{
struct device_node *upd_np;
int cnt = 0;
struct platform_device *upd_pdev;
struct rproc *rproc;
for_each_available_child_of_node(q6rproc->dev.parent->of_node, upd_np) {
if (!strstr(upd_np->name, "pd"))
continue;
if (pd_asid == ++cnt) {
upd_pdev = of_find_device_by_node(upd_np);
if (!upd_pdev) {
pr_info("upd pdev is null\n");
return NULL;
}
rproc = platform_get_drvdata(upd_pdev);
return rproc;
}
}
return NULL;
}
static int register_userpd_rproc_notif(struct rproc *q6rproc)
{
struct device_node *upd_np;
for_each_available_child_of_node(q6rproc->dev.parent->of_node, upd_np) {
struct platform_device *upd_pdev;
struct rproc *upd_rproc; int ret;
if (!strstr(upd_np->name, "pd"))
continue;
upd_pdev = of_find_device_by_node(upd_np);
if (!upd_pdev) {
pr_info("upd pdev is null\n");
return -1;
}
upd_rproc = platform_get_drvdata(upd_pdev);
ret = test_rproc_notif_register(upd_rproc->name);
if (ret)
return ret;
}
return 0;
}
static void unregister_userpd_rproc_notif(struct rproc *q6rproc)
{
struct device_node *upd_np;
for_each_available_child_of_node(q6rproc->dev.parent->of_node, upd_np) {
struct platform_device *upd_pdev;
struct rproc *upd_rproc;
if (!strstr(upd_np->name, "pd"))
continue;
upd_pdev = of_find_device_by_node(upd_np);
upd_rproc = platform_get_drvdata(upd_pdev);
rproc_unregister_subsys_notifier(upd_rproc->name, &nb,
&atomic_nb);
}
}
static int create_userpd_debugfs(struct rproc *q6rproc)
{
int ret, cnt, tmp;
char name[40];
/*Get userpd count*/
cnt = get_userpd_cnt(q6rproc);
if (cnt > MAX_USERPD_CNT) {
pr_err("Current implementation don't support %d userpd's\n",
cnt);
return -EINVAL;
}
if (!cnt) {
pr_err("No userpd is registered\n");
return -EINVAL;
}
/*create a sysfs entry to start/stop registered user pd's*/
q6_userpd_debug = debugfs_create_dir("q6v5_userpd_debug", NULL);
if (IS_ERR(q6_userpd_debug)) {
pr_err("Failed to create q6v5 userpd debug directory\n");
return PTR_ERR(q6_userpd_debug);
}
for (tmp = 0; tmp < cnt; tmp++) {
userpd_dbgfs_hdl[tmp] = kzalloc(sizeof(*userpd_dbgfs_hdl[tmp]),
GFP_KERNEL);
if (!userpd_dbgfs_hdl[tmp]) {
pr_err("Failed to allocate memory\n");
ret = PTR_ERR(userpd_dbgfs_hdl[tmp]);
goto err_free_mem;
}
/*get rproc handle*/
userpd_dbgfs_hdl[tmp]->userpd_hdl.rproc =
get_userpd_rproc(q6rproc, tmp+1);
if (!userpd_dbgfs_hdl[tmp]->userpd_hdl.rproc) {
pr_err("failed to get rproc handle\n");
return -1;
}
snprintf(name, sizeof(name), "q6v5_wcss_userpd%d", (tmp + 1));
/*sysfs entry for each userpd*/
userpd_dbgfs_hdl[tmp]->userpd = debugfs_create_dir(name,
q6_userpd_debug);
if (IS_ERR(userpd_dbgfs_hdl[tmp]->userpd)) {
pr_err("Failed to create q6v5 userpd%d debug directory\n",
(tmp + 1));
ret = PTR_ERR(userpd_dbgfs_hdl[tmp]->userpd);
goto err_release_userpd;
}
/*sysfs entry for userpd start/stop*/
userpd_dbgfs_hdl[tmp]->start_stop =
debugfs_create_file("start_stop",
0600, userpd_dbgfs_hdl[tmp]->userpd,
userpd_dbgfs_hdl[tmp],
&userpd_ops);
if (IS_ERR(userpd_dbgfs_hdl[tmp]->start_stop)) {
pr_err("Failed to create q6v5\
userpd%d start/stop\n", (tmp + 1));
ret = PTR_ERR(userpd_dbgfs_hdl[tmp]->start_stop);
goto err_release_userpd_start_stop;
}
}
/* register call back for every userpd*/
ret = register_userpd_rproc_notif(q6rproc);
if (ret)
return ret;
return 0;
for (; tmp >= 0; tmp--) {
err_release_userpd_start_stop:
debugfs_remove(userpd_dbgfs_hdl[tmp]->start_stop);
err_release_userpd:
debugfs_remove(userpd_dbgfs_hdl[tmp]->userpd);
err_free_mem:
kfree(userpd_dbgfs_hdl[tmp]);
}
debugfs_remove(q6_userpd_debug);
return ret;
}
static int remove_userpd_debugfs(struct rproc *q6rproc)
{
int tmp, cnt;
/*Get userpd count*/
cnt = get_userpd_cnt(q6rproc);
if (cnt > MAX_USERPD_CNT) {
pr_err("Current implementation don't support %d userpd's\n",
cnt);
return -EINVAL;
}
if (!cnt) {
pr_err("No userpd is registered\n");
return -EINVAL;
}
/*shutdown any userpd's subsequently rootpd
* also will be shutted down
*/
for (tmp = 0; tmp < cnt; tmp++) {
if (userpd_dbgfs_hdl[tmp]->userpd_hdl.pd_curr_state) {
rproc_shutdown(userpd_dbgfs_hdl[tmp]->
userpd_hdl.rproc);
}
}
for (tmp = 0; tmp < cnt; tmp++) {
debugfs_remove(userpd_dbgfs_hdl[tmp]->start_stop);
debugfs_remove(userpd_dbgfs_hdl[tmp]->userpd);
kfree(userpd_dbgfs_hdl[tmp]);
}
debugfs_remove(q6_userpd_debug);
unregister_userpd_rproc_notif(q6rproc);
return 0;
}
static int __init testssr_init(void)
{
struct rproc *q6rproc;
int ret;
nb.notifier_call = tssr_notifier;
atomic_nb.notifier_call = tssr_notifier;
switch (test_id) {
case 1:
q6rproc = get_rproc_from_phandle();
if (!q6rproc) {
pr_err("could not get rproc..\n");
return -ENODEV;
}
if (mpd) {
ret = create_userpd_debugfs(q6rproc);
if (ret) {
pr_err("Failed to create sysfs entry for\
userpd\n");
return ret;
}
return 0;
}
ret = test_rproc_notif_register(q6rproc->name);
if (ret)
return ret;
ret = rproc_boot(q6rproc);
if (ret) {
pr_err("couldn't boot q6v5: %d\n", ret);
ret = rproc_unregister_subsys_notifier(q6rproc->name,
&nb, &atomic_nb);
return ret;
}
break;
default:
pr_err("Enter a valid test case id\n");
}
return 0;
}
static void __exit testssr_exit(void)
{
struct rproc *q6rproc;
int ret;
switch (test_id) {
case 1:
q6rproc = get_rproc_from_phandle();
if (!q6rproc) {
pr_err("could not get rproc..\n");
return;
}
if (mpd) {
remove_userpd_debugfs(q6rproc);
return;
}
rproc_shutdown(q6rproc);
ret = rproc_unregister_subsys_notifier(q6rproc->name,
&nb, &atomic_nb);
break;
default:
break;
}
}
module_init(testssr_init);
module_exit(testssr_exit);
MODULE_LICENSE("Dual BSD/GPL");