395 lines
9.6 KiB
C
395 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2011-2014, 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.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
#undef DEBUG
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <soc/qcom/hsic_sysmon.h>
|
|
#include <soc/qcom/sysmon.h>
|
|
#include <soc/qcom/subsystem_notif.h>
|
|
#include <soc/qcom/smd.h>
|
|
|
|
#define TX_BUF_SIZE 50
|
|
#define RX_BUF_SIZE 500
|
|
#define TIMEOUT_MS 500
|
|
|
|
enum transports {
|
|
TRANSPORT_SMD,
|
|
TRANSPORT_HSIC,
|
|
};
|
|
|
|
struct sysmon_subsys {
|
|
struct mutex lock;
|
|
struct smd_channel *chan;
|
|
bool chan_open;
|
|
struct completion resp_ready;
|
|
char rx_buf[RX_BUF_SIZE];
|
|
enum transports transport;
|
|
struct device *dev;
|
|
u32 pid;
|
|
struct list_head list;
|
|
};
|
|
|
|
static const char *notif_name[SUBSYS_NOTIF_TYPE_COUNT] = {
|
|
[SUBSYS_BEFORE_SHUTDOWN] = "before_shutdown",
|
|
[SUBSYS_AFTER_SHUTDOWN] = "after_shutdown",
|
|
[SUBSYS_BEFORE_POWERUP] = "before_powerup",
|
|
[SUBSYS_AFTER_POWERUP] = "after_powerup",
|
|
};
|
|
|
|
static LIST_HEAD(sysmon_list);
|
|
static DEFINE_MUTEX(sysmon_list_lock);
|
|
|
|
static int sysmon_send_smd(struct sysmon_subsys *ss, const char *tx_buf,
|
|
size_t len)
|
|
{
|
|
int ret;
|
|
|
|
if (!ss->chan_open)
|
|
return -ENODEV;
|
|
|
|
init_completion(&ss->resp_ready);
|
|
pr_debug("Sending SMD message: %s\n", tx_buf);
|
|
smd_write(ss->chan, tx_buf, len);
|
|
ret = wait_for_completion_timeout(&ss->resp_ready,
|
|
msecs_to_jiffies(TIMEOUT_MS));
|
|
if (!ret)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysmon_send_hsic(struct sysmon_subsys *ss, const char *tx_buf,
|
|
size_t len)
|
|
{
|
|
int ret;
|
|
size_t actual_len;
|
|
|
|
pr_debug("Sending HSIC message: %s\n", tx_buf);
|
|
ret = hsic_sysmon_write(HSIC_SYSMON_DEV_EXT_MODEM,
|
|
tx_buf, len, TIMEOUT_MS);
|
|
if (ret)
|
|
return ret;
|
|
ret = hsic_sysmon_read(HSIC_SYSMON_DEV_EXT_MODEM, ss->rx_buf,
|
|
ARRAY_SIZE(ss->rx_buf), &actual_len, TIMEOUT_MS);
|
|
return ret;
|
|
}
|
|
|
|
static int sysmon_send_msg(struct sysmon_subsys *ss, const char *tx_buf,
|
|
size_t len)
|
|
{
|
|
int ret;
|
|
|
|
switch (ss->transport) {
|
|
case TRANSPORT_SMD:
|
|
ret = sysmon_send_smd(ss, tx_buf, len);
|
|
break;
|
|
case TRANSPORT_HSIC:
|
|
ret = sysmon_send_hsic(ss, tx_buf, len);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (!ret)
|
|
pr_debug("Received response: %s\n", ss->rx_buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* sysmon_send_event_no_qmi() - Notify a subsystem of another's state change
|
|
* @dest_desc: Subsystem descriptor of the subsystem the notification
|
|
* should be sent to
|
|
* @event_desc: Subsystem descriptor of the subsystem that generated the
|
|
* notification
|
|
* @notif: ID of the notification type (ex. SUBSYS_BEFORE_SHUTDOWN)
|
|
*
|
|
* Returns 0 for success, -EINVAL for invalid destination or notification IDs,
|
|
* -ENODEV if the transport channel is not open, -ETIMEDOUT if the destination
|
|
* subsystem does not respond, and -ENOSYS if the destination subsystem
|
|
* responds, but with something other than an acknowledgement.
|
|
*
|
|
* If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
|
|
*/
|
|
int sysmon_send_event_no_qmi(struct subsys_desc *dest_desc,
|
|
struct subsys_desc *event_desc,
|
|
enum subsys_notif_type notif)
|
|
{
|
|
|
|
char tx_buf[TX_BUF_SIZE];
|
|
int ret;
|
|
struct sysmon_subsys *tmp, *ss = NULL;
|
|
const char *event_ss = event_desc->name;
|
|
|
|
mutex_lock(&sysmon_list_lock);
|
|
list_for_each_entry(tmp, &sysmon_list, list)
|
|
if (tmp->pid == dest_desc->sysmon_pid)
|
|
ss = tmp;
|
|
mutex_unlock(&sysmon_list_lock);
|
|
|
|
if (ss == NULL)
|
|
return -EINVAL;
|
|
|
|
if (ss->dev == NULL)
|
|
return -ENODEV;
|
|
|
|
if (notif < 0 || notif >= SUBSYS_NOTIF_TYPE_COUNT || event_ss == NULL ||
|
|
notif_name[notif] == NULL)
|
|
return -EINVAL;
|
|
|
|
snprintf(tx_buf, ARRAY_SIZE(tx_buf), "ssr:%s:%s", event_ss,
|
|
notif_name[notif]);
|
|
|
|
mutex_lock(&ss->lock);
|
|
ret = sysmon_send_msg(ss, tx_buf, strlen(tx_buf));
|
|
if (ret) {
|
|
pr_err("Message sending failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(ss->rx_buf, "ssr:ack")) {
|
|
pr_debug("Unexpected response %s\n", ss->rx_buf);
|
|
ret = -ENOSYS;
|
|
}
|
|
out:
|
|
mutex_unlock(&ss->lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sysmon_send_event_no_qmi);
|
|
|
|
/**
|
|
* sysmon_send_shutdown_no_qmi() - send shutdown command to a subsystem.
|
|
* @dest_desc: Subsystem descriptor of the subsystem to send to
|
|
*
|
|
* Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
|
|
* the SMD transport channel is not open, -ETIMEDOUT if the destination
|
|
* subsystem does not respond, and -ENOSYS if the destination subsystem
|
|
* responds with something unexpected.
|
|
*
|
|
* If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
|
|
*/
|
|
int sysmon_send_shutdown_no_qmi(struct subsys_desc *dest_desc)
|
|
{
|
|
struct sysmon_subsys *tmp, *ss = NULL;
|
|
const char tx_buf[] = "system:shutdown";
|
|
const char expect[] = "system:ack";
|
|
int ret;
|
|
|
|
mutex_lock(&sysmon_list_lock);
|
|
list_for_each_entry(tmp, &sysmon_list, list)
|
|
if (tmp->pid == dest_desc->sysmon_pid)
|
|
ss = tmp;
|
|
mutex_unlock(&sysmon_list_lock);
|
|
|
|
if (ss == NULL)
|
|
return -EINVAL;
|
|
|
|
if (ss->dev == NULL)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&ss->lock);
|
|
ret = sysmon_send_msg(ss, tx_buf, ARRAY_SIZE(tx_buf));
|
|
if (ret) {
|
|
pr_err("Message sending failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(ss->rx_buf, expect)) {
|
|
pr_err("Unexpected response %s\n", ss->rx_buf);
|
|
ret = -ENOSYS;
|
|
}
|
|
out:
|
|
mutex_unlock(&ss->lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sysmon_send_shutdown_no_qmi);
|
|
|
|
/**
|
|
* sysmon_get_reason_no_qmi() - Retrieve failure reason from a subsystem.
|
|
* @dest_desc: Subsystem descriptor of the subsystem to query
|
|
* @buf: Caller-allocated buffer for the returned NUL-terminated reason
|
|
* @len: Length of @buf
|
|
*
|
|
* Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
|
|
* the SMD transport channel is not open, -ETIMEDOUT if the destination
|
|
* subsystem does not respond, and -ENOSYS if the destination subsystem
|
|
* responds with something unexpected.
|
|
*
|
|
* If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
|
|
*/
|
|
int sysmon_get_reason_no_qmi(struct subsys_desc *dest_desc,
|
|
char *buf, size_t len)
|
|
{
|
|
struct sysmon_subsys *tmp, *ss = NULL;
|
|
const char tx_buf[] = "ssr:retrieve:sfr";
|
|
const char expect[] = "ssr:return:";
|
|
size_t prefix_len = ARRAY_SIZE(expect) - 1;
|
|
int ret;
|
|
|
|
mutex_lock(&sysmon_list_lock);
|
|
list_for_each_entry(tmp, &sysmon_list, list)
|
|
if (tmp->pid == dest_desc->sysmon_pid)
|
|
ss = tmp;
|
|
mutex_unlock(&sysmon_list_lock);
|
|
|
|
if (ss == NULL || buf == NULL || len == 0)
|
|
return -EINVAL;
|
|
|
|
if (ss->dev == NULL)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&ss->lock);
|
|
ret = sysmon_send_msg(ss, tx_buf, ARRAY_SIZE(tx_buf));
|
|
if (ret) {
|
|
pr_err("Message sending failed %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (strncmp(ss->rx_buf, expect, prefix_len)) {
|
|
pr_err("Unexpected response %s\n", ss->rx_buf);
|
|
ret = -ENOSYS;
|
|
goto out;
|
|
}
|
|
strlcpy(buf, ss->rx_buf + prefix_len, len);
|
|
out:
|
|
mutex_unlock(&ss->lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sysmon_get_reason_no_qmi);
|
|
|
|
static void sysmon_smd_notify(void *priv, unsigned int smd_event)
|
|
{
|
|
struct sysmon_subsys *ss = priv;
|
|
|
|
switch (smd_event) {
|
|
case SMD_EVENT_DATA: {
|
|
if (smd_read_avail(ss->chan) > 0) {
|
|
smd_read_from_cb(ss->chan, ss->rx_buf,
|
|
ARRAY_SIZE(ss->rx_buf));
|
|
complete(&ss->resp_ready);
|
|
}
|
|
break;
|
|
}
|
|
case SMD_EVENT_OPEN:
|
|
ss->chan_open = true;
|
|
break;
|
|
case SMD_EVENT_CLOSE:
|
|
ss->chan_open = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int sysmon_probe(struct platform_device *pdev)
|
|
{
|
|
struct sysmon_subsys *ss;
|
|
int ret;
|
|
|
|
if (pdev->id < 0 || pdev->id >= SYSMON_NUM_SS)
|
|
return -ENODEV;
|
|
|
|
ss = devm_kzalloc(&pdev->dev, sizeof(*ss), GFP_KERNEL);
|
|
if (!ss)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&ss->lock);
|
|
if (pdev->id == SYSMON_SS_EXT_MODEM) {
|
|
ss->transport = TRANSPORT_HSIC;
|
|
ret = hsic_sysmon_open(HSIC_SYSMON_DEV_EXT_MODEM);
|
|
if (ret) {
|
|
pr_err("HSIC open failed\n");
|
|
return ret;
|
|
}
|
|
} else if (pdev->id < SMD_NUM_TYPE) {
|
|
ss->transport = TRANSPORT_SMD;
|
|
ret = smd_named_open_on_edge("sys_mon", pdev->id, &ss->chan,
|
|
ss, sysmon_smd_notify);
|
|
if (ret) {
|
|
pr_err("SMD open failed\n");
|
|
return ret;
|
|
}
|
|
smd_disable_read_intr(ss->chan);
|
|
} else
|
|
return -EINVAL;
|
|
|
|
ss->dev = &pdev->dev;
|
|
ss->pid = pdev->id;
|
|
|
|
mutex_lock(&sysmon_list_lock);
|
|
INIT_LIST_HEAD(&ss->list);
|
|
list_add_tail(&ss->list, &sysmon_list);
|
|
mutex_unlock(&sysmon_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int sysmon_remove(struct platform_device *pdev)
|
|
{
|
|
struct sysmon_subsys *sysmon, *tmp, *ss = NULL;
|
|
|
|
mutex_lock(&sysmon_list_lock);
|
|
list_for_each_entry_safe(sysmon, tmp, &sysmon_list, list) {
|
|
if (sysmon->pid == pdev->id) {
|
|
ss = sysmon;
|
|
list_del(&ss->list);
|
|
}
|
|
}
|
|
mutex_unlock(&sysmon_list_lock);
|
|
|
|
if (ss == NULL)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&ss->lock);
|
|
switch (ss->transport) {
|
|
case TRANSPORT_SMD:
|
|
smd_close(ss->chan);
|
|
break;
|
|
case TRANSPORT_HSIC:
|
|
hsic_sysmon_close(HSIC_SYSMON_DEV_EXT_MODEM);
|
|
break;
|
|
}
|
|
mutex_unlock(&ss->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver sysmon_driver = {
|
|
.probe = sysmon_probe,
|
|
.remove = sysmon_remove,
|
|
.driver = {
|
|
.name = "sys_mon",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init sysmon_init(void)
|
|
{
|
|
return platform_driver_register(&sysmon_driver);
|
|
}
|
|
subsys_initcall(sysmon_init);
|
|
|
|
static void __exit sysmon_exit(void)
|
|
{
|
|
platform_driver_unregister(&sysmon_driver);
|
|
}
|
|
module_exit(sysmon_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("system monitor communication library");
|
|
MODULE_ALIAS("platform:sys_mon");
|