Add snapshot for Subsystem Restart (SSR), Peripheral Image Loader framework (PIL), Secure PIL driver, System Monitor (sysmon), and Protection Domain Restart (PDR) drivers from msm-4.19 as of commit <61502d56cb5f9b1> (Merge "drivers: llcc_perfmon: support for dual memory controller added"). Change-Id: Id06c338121486705dfd716567244650b4dd1742c [rananta@codeaurora.org: Make changes to make the drivers modular] Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
764 lines
22 KiB
C
764 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "service-notifier: %s: " fmt, __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/err.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <soc/qcom/subsystem_restart.h>
|
|
#include <soc/qcom/subsystem_notif.h>
|
|
#include <soc/qcom/sysmon.h>
|
|
#include <soc/qcom/service-locator.h>
|
|
#include <soc/qcom/service-notifier.h>
|
|
#include "service-notifier-private.h"
|
|
|
|
#define SERVREG_NOTIF_NAME_LENGTH QMI_SERVREG_NOTIF_NAME_LENGTH_V01
|
|
#define SERVREG_NOTIF_SERVICE_ID SERVREG_NOTIF_SERVICE_ID_V01
|
|
#define SERVREG_NOTIF_SERVICE_VERS SERVREG_NOTIF_SERVICE_VERS_V01
|
|
|
|
#define SERVREG_NOTIF_SET_ACK_REQ \
|
|
QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_REQ_V01
|
|
#define SERVREG_NOTIF_SET_ACK_REQ_MSG_LEN \
|
|
QMI_SERVREG_NOTIF_SET_ACK_REQ_MSG_V01_MAX_MSG_LEN
|
|
#define SERVREG_NOTIF_SET_ACK_RESP \
|
|
QMI_SERVREG_NOTIF_STATE_UPDATED_IND_ACK_RESP_V01
|
|
#define SERVREG_NOTIF_SET_ACK_RESP_MSG_LEN \
|
|
QMI_SERVREG_NOTIF_SET_ACK_RESP_MSG_V01_MAX_MSG_LEN
|
|
#define SERVREG_NOTIF_STATE_UPDATED_IND_MSG \
|
|
QMI_SERVREG_NOTIF_STATE_UPDATED_IND_V01
|
|
#define SERVREG_NOTIF_STATE_UPDATED_IND_MSG_LEN \
|
|
QMI_SERVREG_NOTIF_STATE_UPDATED_IND_MSG_V01_MAX_MSG_LEN
|
|
|
|
#define SERVREG_NOTIF_REGISTER_LISTENER_REQ \
|
|
QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_V01
|
|
#define SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_LEN \
|
|
QMI_SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_V01_MAX_MSG_LEN
|
|
#define SERVREG_NOTIF_REGISTER_LISTENER_RESP \
|
|
QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_V01
|
|
#define SERVREG_NOTIF_REGISTER_LISTENER_RESP_MSG_LEN \
|
|
QMI_SERVREG_NOTIF_REGISTER_LISTENER_RESP_MSG_V01_MAX_MSG_LEN
|
|
|
|
#define QMI_STATE_MIN_VAL QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MIN_VAL_V01
|
|
#define QMI_STATE_MAX_VAL QMI_SERVREG_NOTIF_SERVICE_STATE_ENUM_TYPE_MAX_VAL_V01
|
|
|
|
#define SERVER_TIMEOUT 500
|
|
#define MAX_STRING_LEN 100
|
|
|
|
/*
|
|
* Per user service data structure
|
|
* struct service_notif_info - notifier struct for each unique service path
|
|
* service_path - service provider path/location
|
|
* instance_id - service instance id specific to a subsystem
|
|
* service_notif_rcvr_list - list of clients interested in this service
|
|
* providers notifications
|
|
* curr_state: Current state of the service
|
|
*/
|
|
struct service_notif_info {
|
|
char service_path[SERVREG_NOTIF_NAME_LENGTH];
|
|
int instance_id;
|
|
struct srcu_notifier_head service_notif_rcvr_list;
|
|
struct list_head list;
|
|
int curr_state;
|
|
};
|
|
static LIST_HEAD(service_list);
|
|
static DEFINE_MUTEX(service_list_lock);
|
|
|
|
struct ind_req_resp {
|
|
int transaction_id;
|
|
int curr_state;
|
|
struct work_struct ind_ack;
|
|
struct qmi_client_info *client_data;
|
|
};
|
|
|
|
/*
|
|
* Per Root Process Domain (Root service) data structure
|
|
* struct qmi_client_info - QMI client info for each subsystem/instance id
|
|
* instance_id - service instance id specific to a subsystem (Root PD)
|
|
* clnt_handle - unique QMI client handle
|
|
* service_connected - indicates if QMI service is up on the subsystem
|
|
* ssr_handle - The SSR handle provided by the SSR driver for the subsystem
|
|
* on which the remote root PD runs.
|
|
*/
|
|
struct qmi_client_info {
|
|
int instance_id;
|
|
char service_path[SERVREG_NOTIF_NAME_LENGTH];
|
|
enum pd_subsys_state subsys_state;
|
|
struct work_struct new_server;
|
|
struct work_struct del_server;
|
|
struct workqueue_struct *svc_event_wq;
|
|
struct workqueue_struct *ind_ack_wq;
|
|
struct qmi_handle clnt_handle;
|
|
struct notifier_block notifier;
|
|
void *ssr_handle;
|
|
struct notifier_block ssr_notifier;
|
|
bool service_connected;
|
|
struct list_head list;
|
|
struct sockaddr_qrtr s_addr;
|
|
};
|
|
static LIST_HEAD(qmi_client_list);
|
|
static DEFINE_MUTEX(qmi_list_lock);
|
|
static DEFINE_MUTEX(notif_add_lock);
|
|
|
|
static struct service_notif_info *_find_service_info(const char *service_path)
|
|
{
|
|
struct service_notif_info *service_notif;
|
|
|
|
mutex_lock(&service_list_lock);
|
|
list_for_each_entry(service_notif, &service_list, list)
|
|
if (!strcmp(service_notif->service_path, service_path)) {
|
|
mutex_unlock(&service_list_lock);
|
|
return service_notif;
|
|
}
|
|
mutex_unlock(&service_list_lock);
|
|
return NULL;
|
|
}
|
|
|
|
static int service_notif_queue_notification(struct service_notif_info
|
|
*service_notif,
|
|
enum qmi_servreg_notif_service_state_enum_type_v01 notif_type,
|
|
void *info)
|
|
{
|
|
int ret;
|
|
|
|
if (service_notif->curr_state == notif_type)
|
|
return 0;
|
|
|
|
ret = srcu_notifier_call_chain(&service_notif->service_notif_rcvr_list,
|
|
notif_type, info);
|
|
return ret;
|
|
}
|
|
|
|
static void send_ind_ack(struct work_struct *work)
|
|
{
|
|
struct ind_req_resp *ind_info = container_of(work, struct ind_req_resp,
|
|
ind_ack);
|
|
struct qmi_client_info *data = ind_info->client_data;
|
|
struct qmi_servreg_notif_set_ack_req_msg_v01 req;
|
|
struct qmi_servreg_notif_set_ack_resp_msg_v01 resp = { { 0, 0 } };
|
|
struct qmi_txn txn;
|
|
struct service_notif_info *service_notif;
|
|
enum pd_subsys_state state = USER_PD_STATE_CHANGE;
|
|
int rc;
|
|
|
|
service_notif = _find_service_info(data->service_path);
|
|
if (!service_notif)
|
|
goto out;
|
|
if ((int)ind_info->curr_state < QMI_STATE_MIN_VAL ||
|
|
(int)ind_info->curr_state > QMI_STATE_MAX_VAL)
|
|
pr_err("Unexpected indication notification state %d\n",
|
|
ind_info->curr_state);
|
|
else {
|
|
mutex_lock(¬if_add_lock);
|
|
mutex_lock(&service_list_lock);
|
|
rc = service_notif_queue_notification(service_notif,
|
|
ind_info->curr_state, &state);
|
|
if (rc & NOTIFY_STOP_MASK)
|
|
pr_err("Notifier callback aborted for %s with error %d\n",
|
|
data->service_path, rc);
|
|
service_notif->curr_state = ind_info->curr_state;
|
|
mutex_unlock(&service_list_lock);
|
|
mutex_unlock(¬if_add_lock);
|
|
}
|
|
|
|
req.transaction_id = ind_info->transaction_id;
|
|
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
|
data->service_path);
|
|
|
|
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
|
qmi_servreg_notif_set_ack_resp_msg_v01_ei,
|
|
&resp);
|
|
|
|
if (rc < 0) {
|
|
pr_err("%s QMI tx init failed , ret - %d\n",
|
|
data->service_path, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
|
&txn, SERVREG_NOTIF_SET_ACK_REQ,
|
|
SERVREG_NOTIF_SET_ACK_REQ_MSG_LEN,
|
|
qmi_servreg_notif_set_ack_req_msg_v01_ei,
|
|
&req);
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI send ACK failed, ret - %d\n",
|
|
data->service_path, rc);
|
|
qmi_txn_cancel(&txn);
|
|
goto out;
|
|
}
|
|
|
|
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI qmi txn wait failed, ret - %d\n",
|
|
data->service_path, rc);
|
|
goto out;
|
|
}
|
|
|
|
/* Check the response */
|
|
if (resp.resp.result != QMI_RESULT_SUCCESS_V01)
|
|
pr_err("QMI request failed 0x%x\n", resp.resp.error);
|
|
pr_info("Indication ACKed for transid %d, service %s, instance %d!\n",
|
|
ind_info->transaction_id, data->service_path,
|
|
data->instance_id);
|
|
out:
|
|
kfree(ind_info);
|
|
}
|
|
|
|
static void root_service_service_ind_cb(struct qmi_handle *qmi,
|
|
struct sockaddr_qrtr *sq,
|
|
struct qmi_txn *txn, const void *data)
|
|
{
|
|
struct qmi_client_info *qmi_data = container_of(qmi,
|
|
struct qmi_client_info, clnt_handle);
|
|
struct qmi_servreg_notif_state_updated_ind_msg_v01 ind_msg =
|
|
*((struct qmi_servreg_notif_state_updated_ind_msg_v01 *)data);
|
|
struct ind_req_resp *ind_info = kmalloc(sizeof(*ind_info), GFP_KERNEL);
|
|
|
|
if (!ind_info)
|
|
return;
|
|
|
|
pr_info("Indication received from %s, state: 0x%x, trans-id: %d\n",
|
|
ind_msg.service_name, ind_msg.curr_state,
|
|
ind_msg.transaction_id);
|
|
|
|
ind_info->transaction_id = ind_msg.transaction_id;
|
|
ind_info->curr_state = ind_msg.curr_state;
|
|
ind_info->client_data = qmi_data;
|
|
INIT_WORK(&ind_info->ind_ack, send_ind_ack);
|
|
queue_work(qmi_data->ind_ack_wq, &ind_info->ind_ack);
|
|
}
|
|
|
|
static int send_notif_listener_msg_req(struct service_notif_info *service_notif,
|
|
struct qmi_client_info *data,
|
|
bool register_notif, int *curr_state)
|
|
{
|
|
struct qmi_servreg_notif_register_listener_req_msg_v01 req;
|
|
struct qmi_servreg_notif_register_listener_resp_msg_v01
|
|
resp = { { 0, 0 } };
|
|
struct qmi_txn txn;
|
|
int rc;
|
|
|
|
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
|
service_notif->service_path);
|
|
req.enable = register_notif;
|
|
|
|
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
|
qmi_servreg_notif_register_listener_resp_msg_v01_ei,
|
|
&resp);
|
|
|
|
if (rc < 0) {
|
|
pr_err("%s QMI tx init failed , ret - %d\n",
|
|
service_notif->service_path, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
|
&txn, SERVREG_NOTIF_REGISTER_LISTENER_REQ,
|
|
SERVREG_NOTIF_REGISTER_LISTENER_REQ_MSG_LEN,
|
|
qmi_servreg_notif_register_listener_req_msg_v01_ei,
|
|
&req);
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI send req failed, ret - %d\n",
|
|
service_notif->service_path, rc);
|
|
qmi_txn_cancel(&txn);
|
|
return rc;
|
|
}
|
|
|
|
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI qmi txn wait failed, ret - %d\n",
|
|
service_notif->service_path, rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Check the response */
|
|
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
|
|
pr_err("QMI request failed 0x%x\n", resp.resp.error);
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
if ((int) resp.curr_state < QMI_STATE_MIN_VAL ||
|
|
(int) resp.curr_state > QMI_STATE_MAX_VAL) {
|
|
pr_err("Invalid indication notification state %d\n",
|
|
resp.curr_state);
|
|
rc = -EINVAL;
|
|
}
|
|
*curr_state = resp.curr_state;
|
|
return rc;
|
|
}
|
|
|
|
static struct qmi_msg_handler qmi_indication_handler[] = {
|
|
{
|
|
.type = QMI_INDICATION,
|
|
.msg_id = SERVREG_NOTIF_STATE_UPDATED_IND_MSG,
|
|
.ei = qmi_servreg_notif_state_updated_ind_msg_v01_ei,
|
|
.decoded_size =
|
|
sizeof(struct qmi_servreg_notif_state_updated_ind_msg_v01),
|
|
.fn = root_service_service_ind_cb,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int register_notif_listener(struct service_notif_info *service_notif,
|
|
struct qmi_client_info *data,
|
|
int *curr_state)
|
|
{
|
|
return send_notif_listener_msg_req(service_notif, data, true,
|
|
curr_state);
|
|
}
|
|
|
|
static int service_notifier_new_server(struct qmi_handle *qmi,
|
|
struct qmi_service *svc)
|
|
{
|
|
struct qmi_client_info *data = container_of(qmi,
|
|
struct qmi_client_info, clnt_handle);
|
|
|
|
data->s_addr.sq_family = AF_QIPCRTR;
|
|
data->s_addr.sq_node = svc->node;
|
|
data->s_addr.sq_port = svc->port;
|
|
data->service_connected = true;
|
|
pr_info("Connection established between QMI handle and %d service\n",
|
|
data->instance_id);
|
|
queue_work(data->svc_event_wq, &data->new_server);
|
|
return 0;
|
|
}
|
|
|
|
static void new_server_work(struct work_struct *work)
|
|
{
|
|
struct service_notif_info *service_notif = NULL;
|
|
struct qmi_client_info *data = container_of(work,
|
|
struct qmi_client_info, new_server);
|
|
int rc = 0, curr_state = 0;
|
|
|
|
mutex_lock(¬if_add_lock);
|
|
mutex_lock(&service_list_lock);
|
|
list_for_each_entry(service_notif, &service_list, list) {
|
|
if (service_notif->instance_id == data->instance_id && !strcmp
|
|
(service_notif->service_path, data->service_path)) {
|
|
enum pd_subsys_state state = ROOT_PD_UP;
|
|
|
|
rc = register_notif_listener(service_notif, data,
|
|
&curr_state);
|
|
if (rc < 0) {
|
|
pr_err("Notifier registration failed for %s rc:%d\n",
|
|
service_notif->service_path, rc);
|
|
} else {
|
|
rc = service_notif_queue_notification(
|
|
service_notif, curr_state, &state);
|
|
if (rc & NOTIFY_STOP_MASK)
|
|
pr_err("Notifier callback aborted for %s error:%d\n",
|
|
service_notif->service_path, rc);
|
|
service_notif->curr_state = curr_state;
|
|
}
|
|
}
|
|
}
|
|
mutex_unlock(&service_list_lock);
|
|
mutex_unlock(¬if_add_lock);
|
|
}
|
|
|
|
static void root_service_service_exit(struct qmi_client_info *data,
|
|
enum pd_subsys_state state)
|
|
{
|
|
struct service_notif_info *service_notif = NULL;
|
|
int rc;
|
|
|
|
/*
|
|
* Send service down notifications to all clients
|
|
* of registered for notifications for that service.
|
|
*/
|
|
mutex_lock(¬if_add_lock);
|
|
mutex_lock(&service_list_lock);
|
|
list_for_each_entry(service_notif, &service_list, list) {
|
|
if (service_notif->instance_id == data->instance_id && !strcmp
|
|
(data->service_path, service_notif->service_path)) {
|
|
rc = service_notif_queue_notification(service_notif,
|
|
SERVREG_NOTIF_SERVICE_STATE_DOWN_V01,
|
|
&state);
|
|
if (rc & NOTIFY_STOP_MASK)
|
|
pr_err("Notifier callback aborted for %s with error %d\n",
|
|
service_notif->service_path, rc);
|
|
service_notif->curr_state =
|
|
SERVREG_NOTIF_SERVICE_STATE_DOWN_V01;
|
|
}
|
|
}
|
|
mutex_unlock(&service_list_lock);
|
|
mutex_unlock(¬if_add_lock);
|
|
}
|
|
|
|
static int ssr_event_notify(struct notifier_block *this,
|
|
unsigned long code,
|
|
void *data)
|
|
{
|
|
struct qmi_client_info *info = container_of(this,
|
|
struct qmi_client_info, ssr_notifier);
|
|
struct notif_data *notif = data;
|
|
|
|
switch (code) {
|
|
case SUBSYS_BEFORE_SHUTDOWN:
|
|
pr_debug("Root PD DOWN(SSR notification), state:%d\n",
|
|
notif->crashed);
|
|
switch (notif->crashed) {
|
|
case CRASH_STATUS_ERR_FATAL:
|
|
info->subsys_state = ROOT_PD_ERR_FATAL;
|
|
break;
|
|
case CRASH_STATUS_WDOG_BITE:
|
|
info->subsys_state = ROOT_PD_WDOG_BITE;
|
|
break;
|
|
default:
|
|
info->subsys_state = ROOT_PD_SHUTDOWN;
|
|
break;
|
|
}
|
|
root_service_service_exit(info, info->subsys_state);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static void del_server_work(struct work_struct *work)
|
|
{
|
|
struct qmi_client_info *data = container_of(work,
|
|
struct qmi_client_info, del_server);
|
|
|
|
data->subsys_state = ROOT_PD_DOWN;
|
|
root_service_service_exit(data, data->subsys_state);
|
|
}
|
|
|
|
static void service_notifier_del_server(struct qmi_handle *qmi,
|
|
struct qmi_service *svc)
|
|
{
|
|
struct qmi_client_info *data = container_of(qmi,
|
|
struct qmi_client_info, clnt_handle);
|
|
data->service_connected = false;
|
|
pr_info("Connection lost between QMI handle and %d service\n",
|
|
data->instance_id);
|
|
queue_work(data->svc_event_wq, &data->del_server);
|
|
}
|
|
|
|
static struct qmi_ops server_ops = {
|
|
.new_server = service_notifier_new_server,
|
|
.del_server = service_notifier_del_server,
|
|
};
|
|
|
|
static void *add_service_notif(const char *service_path, int instance_id,
|
|
int *curr_state)
|
|
{
|
|
struct service_notif_info *service_notif;
|
|
struct qmi_client_info *tmp, *qmi_data;
|
|
long rc;
|
|
char subsys[SERVREG_NOTIF_NAME_LENGTH];
|
|
|
|
rc = find_subsys(service_path, subsys);
|
|
if (rc < 0) {
|
|
pr_err("Could not find subsys for %s\n", service_path);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
service_notif = kzalloc(sizeof(struct service_notif_info), GFP_KERNEL);
|
|
if (!service_notif)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
strlcpy(service_notif->service_path, service_path,
|
|
ARRAY_SIZE(service_notif->service_path));
|
|
service_notif->instance_id = instance_id;
|
|
|
|
/* If we already have a connection to the root PD on which the remote
|
|
* service we are interested in notifications about runs, then use
|
|
* the existing QMI connection.
|
|
*/
|
|
mutex_lock(&qmi_list_lock);
|
|
list_for_each_entry(tmp, &qmi_client_list, list) {
|
|
if (tmp->instance_id == instance_id && !strcmp
|
|
(tmp->service_path, service_path)) {
|
|
if (tmp->service_connected) {
|
|
rc = register_notif_listener(service_notif, tmp,
|
|
curr_state);
|
|
if (rc < 0) {
|
|
mutex_unlock(&qmi_list_lock);
|
|
pr_err("Register notifier failed: %s\n",
|
|
service_path);
|
|
kfree(service_notif);
|
|
return ERR_PTR(rc);
|
|
}
|
|
service_notif->curr_state = *curr_state;
|
|
}
|
|
mutex_unlock(&qmi_list_lock);
|
|
goto add_service_list;
|
|
}
|
|
}
|
|
mutex_unlock(&qmi_list_lock);
|
|
|
|
qmi_data = kzalloc(sizeof(struct qmi_client_info), GFP_KERNEL);
|
|
if (!qmi_data) {
|
|
kfree(service_notif);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
qmi_data->instance_id = instance_id;
|
|
strlcpy(qmi_data->service_path, service_path,
|
|
ARRAY_SIZE(service_notif->service_path));
|
|
qmi_data->svc_event_wq = create_singlethread_workqueue(subsys);
|
|
if (!qmi_data->svc_event_wq) {
|
|
rc = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
qmi_data->ind_ack_wq = alloc_ordered_workqueue("%s_pdr_wq", WQ_HIGHPRI,
|
|
subsys);
|
|
if (!qmi_data->ind_ack_wq) {
|
|
rc = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
INIT_WORK(&qmi_data->new_server, new_server_work);
|
|
INIT_WORK(&qmi_data->del_server, del_server_work);
|
|
|
|
*curr_state = service_notif->curr_state =
|
|
SERVREG_NOTIF_SERVICE_STATE_UNINIT_V01;
|
|
|
|
rc = qmi_handle_init(&qmi_data->clnt_handle,
|
|
SERVREG_NOTIF_STATE_UPDATED_IND_MSG_LEN,
|
|
&server_ops,
|
|
qmi_indication_handler);
|
|
if (rc < 0) {
|
|
pr_err("Service Notifier qmi handle init failed rc:%ld\n", rc);
|
|
goto exit;
|
|
}
|
|
|
|
qmi_add_lookup(&qmi_data->clnt_handle,
|
|
SERVREG_NOTIF_SERVICE_ID,
|
|
SERVREG_NOTIF_SERVICE_VERS_V01,
|
|
instance_id);
|
|
|
|
qmi_data->ssr_notifier.notifier_call = ssr_event_notify;
|
|
qmi_data->ssr_handle = subsys_notif_register_notifier(subsys,
|
|
&qmi_data->ssr_notifier);
|
|
if (IS_ERR(qmi_data->ssr_handle)) {
|
|
pr_err("SSR notif register for %s failed(instance-id: %d)\n",
|
|
subsys, qmi_data->instance_id);
|
|
rc = PTR_ERR(qmi_data->ssr_handle);
|
|
qmi_handle_release(&qmi_data->clnt_handle);
|
|
goto exit;
|
|
}
|
|
|
|
mutex_lock(&qmi_list_lock);
|
|
INIT_LIST_HEAD(&qmi_data->list);
|
|
list_add_tail(&qmi_data->list, &qmi_client_list);
|
|
mutex_unlock(&qmi_list_lock);
|
|
|
|
add_service_list:
|
|
srcu_init_notifier_head(&service_notif->service_notif_rcvr_list);
|
|
|
|
mutex_lock(&service_list_lock);
|
|
INIT_LIST_HEAD(&service_notif->list);
|
|
list_add_tail(&service_notif->list, &service_list);
|
|
mutex_unlock(&service_list_lock);
|
|
|
|
return service_notif;
|
|
exit:
|
|
if (qmi_data->ind_ack_wq)
|
|
destroy_workqueue(qmi_data->ind_ack_wq);
|
|
if (qmi_data->svc_event_wq)
|
|
destroy_workqueue(qmi_data->svc_event_wq);
|
|
kfree(qmi_data);
|
|
kfree(service_notif);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
static int send_pd_restart_req(const char *service_path,
|
|
struct qmi_client_info *data)
|
|
{
|
|
struct qmi_servreg_notif_restart_pd_req_msg_v01 req;
|
|
struct qmi_servreg_notif_register_listener_resp_msg_v01
|
|
resp = { { 0, 0 } };
|
|
struct qmi_txn txn;
|
|
int rc;
|
|
|
|
snprintf(req.service_name, ARRAY_SIZE(req.service_name), "%s",
|
|
service_path);
|
|
|
|
rc = qmi_txn_init(&data->clnt_handle, &txn,
|
|
qmi_servreg_notif_restart_pd_resp_msg_v01_ei,
|
|
&resp);
|
|
|
|
if (rc < 0) {
|
|
pr_err("%s QMI tx init failed , ret - %d\n", service_path, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qmi_send_request(&data->clnt_handle, &data->s_addr,
|
|
&txn, QMI_SERVREG_NOTIF_RESTART_PD_REQ_V01,
|
|
QMI_SERVREG_NOTIF_RESTART_PD_REQ_MSG_V01_MAX_MSG_LEN,
|
|
qmi_servreg_notif_restart_pd_req_msg_v01_ei,
|
|
&req);
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI send req failed, ret - %d\n",
|
|
service_path, rc);
|
|
qmi_txn_cancel(&txn);
|
|
return rc;
|
|
}
|
|
|
|
rc = qmi_txn_wait(&txn, msecs_to_jiffies(SERVER_TIMEOUT));
|
|
if (rc < 0) {
|
|
pr_err("%s: QMI qmi txn wait failed for client, ret - %d\n",
|
|
service_path, rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Check response if PDR is disabled */
|
|
if (resp.resp.result == QMI_RESULT_FAILURE_V01 &&
|
|
resp.resp.error == QMI_ERR_DISABLED_V01) {
|
|
pr_err("PD restart is disabled 0x%x\n", resp.resp.error);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
/* Check the response for other error case*/
|
|
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
|
|
pr_err("QMI request for PD restart failed 0x%x\n",
|
|
resp.resp.error);
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* service_notif_pd_restart() - Request PD restart
|
|
* @service_path: Individual service identifier path for which restart is
|
|
* being requested.
|
|
* @instance_id: Instance id specific to a subsystem.
|
|
*
|
|
* @return: >=0 on success, standard Linux error codes on failure.
|
|
*/
|
|
int service_notif_pd_restart(const char *service_path, int instance_id)
|
|
{
|
|
struct qmi_client_info *tmp;
|
|
int rc = 0;
|
|
|
|
list_for_each_entry(tmp, &qmi_client_list, list) {
|
|
if (tmp->instance_id == instance_id && !strcmp
|
|
(tmp->service_path, service_path)) {
|
|
if (tmp->service_connected) {
|
|
pr_info("Restarting service %s, instance-id %d\n",
|
|
service_path, instance_id);
|
|
rc = send_pd_restart_req(service_path, tmp);
|
|
} else
|
|
pr_info("Service %s is not connected\n",
|
|
service_path);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(service_notif_pd_restart);
|
|
|
|
/* service_notif_register_notifier() - Register a notifier for a service
|
|
* On success, it returns back a handle. It takes the following arguments:
|
|
* service_path: Individual service identifier path for which a client
|
|
* registers for notifications.
|
|
* instance_id: Instance id specific to a subsystem.
|
|
* current_state: Current state of service returned by the registration
|
|
* process.
|
|
* notifier block: notifier callback for service events.
|
|
*/
|
|
void *service_notif_register_notifier(const char *service_path, int instance_id,
|
|
struct notifier_block *nb, int *curr_state)
|
|
{
|
|
struct service_notif_info *service_notif;
|
|
int ret = 0;
|
|
|
|
if (!service_path || !instance_id || !nb)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
mutex_lock(¬if_add_lock);
|
|
service_notif = _find_service_info(service_path);
|
|
if (!service_notif) {
|
|
service_notif = (struct service_notif_info *)add_service_notif(
|
|
service_path,
|
|
instance_id,
|
|
curr_state);
|
|
if (IS_ERR(service_notif))
|
|
goto exit;
|
|
}
|
|
|
|
ret = srcu_notifier_chain_register(
|
|
&service_notif->service_notif_rcvr_list, nb);
|
|
*curr_state = service_notif->curr_state;
|
|
if (ret < 0)
|
|
service_notif = ERR_PTR(ret);
|
|
exit:
|
|
mutex_unlock(¬if_add_lock);
|
|
return service_notif;
|
|
}
|
|
EXPORT_SYMBOL(service_notif_register_notifier);
|
|
|
|
static void remove_service_notif(struct service_notif_info *service_notif)
|
|
{
|
|
mutex_lock(&service_list_lock);
|
|
list_del(&service_notif->list);
|
|
mutex_unlock(&service_list_lock);
|
|
|
|
kfree(service_notif);
|
|
}
|
|
|
|
/* service_notif_unregister_notifier() - Unregister a notifier for a service.
|
|
* service_notif_handle - The notifier handler that was provided by the
|
|
* service_notif_register_notifier function when the
|
|
* client registered for notifications.
|
|
* nb - The notifier block that was previously used during the registration.
|
|
*/
|
|
int service_notif_unregister_notifier(void *service_notif_handle,
|
|
struct notifier_block *nb)
|
|
{
|
|
int ret;
|
|
struct service_notif_info *service_notif;
|
|
|
|
if (!service_notif_handle || !nb)
|
|
return -EINVAL;
|
|
|
|
service_notif = (struct service_notif_info *)service_notif_handle;
|
|
if (!service_notif)
|
|
return -EINVAL;
|
|
|
|
ret = srcu_notifier_chain_unregister(
|
|
&service_notif->service_notif_rcvr_list, nb);
|
|
|
|
if (!ret)
|
|
remove_service_notif(service_notif);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(service_notif_unregister_notifier);
|
|
|
|
static void __exit service_notifier_exit(void)
|
|
{
|
|
struct qmi_client_info *tmp, *qmi_data;
|
|
|
|
mutex_lock(&qmi_list_lock);
|
|
list_for_each_entry_safe(qmi_data, tmp, &qmi_client_list, list) {
|
|
list_del(&qmi_data->list);
|
|
|
|
qmi_handle_release(&qmi_data->clnt_handle);
|
|
|
|
flush_work(&qmi_data->new_server);
|
|
flush_work(&qmi_data->del_server);
|
|
|
|
if (qmi_data->ind_ack_wq)
|
|
destroy_workqueue(qmi_data->ind_ack_wq);
|
|
if (qmi_data->svc_event_wq)
|
|
destroy_workqueue(qmi_data->svc_event_wq);
|
|
|
|
kfree(qmi_data);
|
|
}
|
|
mutex_unlock(&qmi_list_lock);
|
|
}
|
|
module_exit(service_notifier_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Service Notifier driver");
|