162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. 462306a36Sopenharmony_ci * Copyright (c) 2017, Linaro Ltd. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/completion.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/notifier.h> 1062306a36Sopenharmony_ci#include <linux/rpmsg.h> 1162306a36Sopenharmony_ci#include <linux/rpmsg/qcom_glink.h> 1262306a36Sopenharmony_ci#include <linux/remoteproc/qcom_rproc.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/** 1562306a36Sopenharmony_ci * struct do_cleanup_msg - The data structure for an SSR do_cleanup message 1662306a36Sopenharmony_ci * @version: The G-Link SSR protocol version 1762306a36Sopenharmony_ci * @command: The G-Link SSR command - do_cleanup 1862306a36Sopenharmony_ci * @seq_num: Sequence number 1962306a36Sopenharmony_ci * @name_len: Length of the name of the subsystem being restarted 2062306a36Sopenharmony_ci * @name: G-Link edge name of the subsystem being restarted 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_cistruct do_cleanup_msg { 2362306a36Sopenharmony_ci __le32 version; 2462306a36Sopenharmony_ci __le32 command; 2562306a36Sopenharmony_ci __le32 seq_num; 2662306a36Sopenharmony_ci __le32 name_len; 2762306a36Sopenharmony_ci char name[32]; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/** 3162306a36Sopenharmony_ci * struct cleanup_done_msg - The data structure for an SSR cleanup_done message 3262306a36Sopenharmony_ci * @version: The G-Link SSR protocol version 3362306a36Sopenharmony_ci * @response: The G-Link SSR response to a do_cleanup command, cleanup_done 3462306a36Sopenharmony_ci * @seq_num: Sequence number 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_cistruct cleanup_done_msg { 3762306a36Sopenharmony_ci __le32 version; 3862306a36Sopenharmony_ci __le32 response; 3962306a36Sopenharmony_ci __le32 seq_num; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/* 4362306a36Sopenharmony_ci * G-Link SSR protocol commands 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_ci#define GLINK_SSR_DO_CLEANUP 0 4662306a36Sopenharmony_ci#define GLINK_SSR_CLEANUP_DONE 1 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct glink_ssr { 4962306a36Sopenharmony_ci struct device *dev; 5062306a36Sopenharmony_ci struct rpmsg_endpoint *ept; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci struct notifier_block nb; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci u32 seq_num; 5562306a36Sopenharmony_ci struct completion completion; 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* Notifier list for all registered glink_ssr instances */ 5962306a36Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(ssr_notifiers); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/** 6262306a36Sopenharmony_ci * qcom_glink_ssr_notify() - notify GLINK SSR about stopped remoteproc 6362306a36Sopenharmony_ci * @ssr_name: name of the remoteproc that has been stopped 6462306a36Sopenharmony_ci */ 6562306a36Sopenharmony_civoid qcom_glink_ssr_notify(const char *ssr_name) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci blocking_notifier_call_chain(&ssr_notifiers, 0, (void *)ssr_name); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_glink_ssr_notify); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic int qcom_glink_ssr_callback(struct rpmsg_device *rpdev, 7262306a36Sopenharmony_ci void *data, int len, void *priv, u32 addr) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct cleanup_done_msg *msg = data; 7562306a36Sopenharmony_ci struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (len < sizeof(*msg)) { 7862306a36Sopenharmony_ci dev_err(ssr->dev, "message too short\n"); 7962306a36Sopenharmony_ci return -EINVAL; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (le32_to_cpu(msg->version) != 0) 8362306a36Sopenharmony_ci return -EINVAL; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (le32_to_cpu(msg->response) != GLINK_SSR_CLEANUP_DONE) 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (le32_to_cpu(msg->seq_num) != ssr->seq_num) { 8962306a36Sopenharmony_ci dev_err(ssr->dev, "invalid sequence number of response\n"); 9062306a36Sopenharmony_ci return -EINVAL; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci complete(&ssr->completion); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return 0; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int qcom_glink_ssr_notifier_call(struct notifier_block *nb, 9962306a36Sopenharmony_ci unsigned long event, 10062306a36Sopenharmony_ci void *data) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct glink_ssr *ssr = container_of(nb, struct glink_ssr, nb); 10362306a36Sopenharmony_ci struct do_cleanup_msg msg; 10462306a36Sopenharmony_ci char *ssr_name = data; 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci ssr->seq_num++; 10862306a36Sopenharmony_ci reinit_completion(&ssr->completion); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci memset(&msg, 0, sizeof(msg)); 11162306a36Sopenharmony_ci msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP); 11262306a36Sopenharmony_ci msg.seq_num = cpu_to_le32(ssr->seq_num); 11362306a36Sopenharmony_ci msg.name_len = cpu_to_le32(strlen(ssr_name)); 11462306a36Sopenharmony_ci strscpy(msg.name, ssr_name, sizeof(msg.name)); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci ret = rpmsg_send(ssr->ept, &msg, sizeof(msg)); 11762306a36Sopenharmony_ci if (ret < 0) 11862306a36Sopenharmony_ci dev_err(ssr->dev, "failed to send cleanup message\n"); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci ret = wait_for_completion_timeout(&ssr->completion, HZ); 12162306a36Sopenharmony_ci if (!ret) 12262306a36Sopenharmony_ci dev_err(ssr->dev, "timeout waiting for cleanup done message\n"); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return NOTIFY_DONE; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int qcom_glink_ssr_probe(struct rpmsg_device *rpdev) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct glink_ssr *ssr; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci ssr = devm_kzalloc(&rpdev->dev, sizeof(*ssr), GFP_KERNEL); 13262306a36Sopenharmony_ci if (!ssr) 13362306a36Sopenharmony_ci return -ENOMEM; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci init_completion(&ssr->completion); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci ssr->dev = &rpdev->dev; 13862306a36Sopenharmony_ci ssr->ept = rpdev->ept; 13962306a36Sopenharmony_ci ssr->nb.notifier_call = qcom_glink_ssr_notifier_call; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci dev_set_drvdata(&rpdev->dev, ssr); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return blocking_notifier_chain_register(&ssr_notifiers, &ssr->nb); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic void qcom_glink_ssr_remove(struct rpmsg_device *rpdev) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci blocking_notifier_chain_unregister(&ssr_notifiers, &ssr->nb); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic const struct rpmsg_device_id qcom_glink_ssr_match[] = { 15462306a36Sopenharmony_ci { "glink_ssr" }, 15562306a36Sopenharmony_ci {} 15662306a36Sopenharmony_ci}; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic struct rpmsg_driver qcom_glink_ssr_driver = { 15962306a36Sopenharmony_ci .probe = qcom_glink_ssr_probe, 16062306a36Sopenharmony_ci .remove = qcom_glink_ssr_remove, 16162306a36Sopenharmony_ci .callback = qcom_glink_ssr_callback, 16262306a36Sopenharmony_ci .id_table = qcom_glink_ssr_match, 16362306a36Sopenharmony_ci .drv = { 16462306a36Sopenharmony_ci .name = "qcom_glink_ssr", 16562306a36Sopenharmony_ci }, 16662306a36Sopenharmony_ci}; 16762306a36Sopenharmony_cimodule_rpmsg_driver(qcom_glink_ssr_driver); 168