Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc17f70c authored by Chris Lew's avatar Chris Lew
Browse files

soc: qcom: glink_probe: Add SSR support for GLINK



RPMSG GLINK should be registered and unregisted as the remote processor
comes up and down. During probe time, register a notifier block for each
subsystem specified in device tree.

The GLINK_SSR channel is part of the SSR handling for the GLINK RPMSG
framework. Register as a rpmsg driver for the GLINK SSR driver and send
the required SSR notifications to interested subsystems.

The GLINK subsystem names differ from the PIL image names. Add a device
tree property to keep track of the GLINK name and the PIL names.

Change-Id: Ic6d5130a1f15776e51ba6698c7d09d561d9f967f
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent 69046731
Loading
Loading
Loading
Loading
+36 −5
Original line number Diff line number Diff line
@@ -13,26 +13,57 @@ The GLINK probe node must contain subnodes that describes the
edge-pairs. See qcom,glink.txt for details on how to describe them.

In addition to the properties in qcom,glink.txt, The GLINK Probe driver
requires the remote-pid and transport type to be specified in the subnodes.
requires the qcom,glink-label and transport type to be specified in the
subnodes.

- transport :
	Usage: required
	Value type: <stringlist>
	Definition: must be "smem", "spss", or "spi"

- qcom,remote-pid :
- qcom,glink-label :
	Usage: required
	Value type: <prop-encoded-array>
	Value type: <stringlist>
	Definition: specifies the identifier of the remote proc of this edge.

= GLINK_SSR
The GLINK probe driver also initializes the GLINK_SSR channel for the edges
that it brings up. The channel should be specified as a subnode to each edge. In
addition to the properties in qcom,glink.txt to specify a channel device node,
the qcom,notify-edges property must be defined.

- qcom,notify-edges :
	Usage: required
	Value type: <prop-encoded-array>
	Definition: list of phandles that specify the subsystems this glink edge
		    needs to receive ssr notifications about.

= EXAMPLE
qcom,glink {
	compatible = "qcom,glink";
	modem {
	glink_modem: modem {
		transport = "smem";
		qcom,remote-pid = <1>;
		qcom,remote-pid = <0>;
		mboxes = <&apcs_glb 8>;
		mbox-names = "mpss_smem";
		interrupts = <GIC_SPI 449 IRQ_TYPE_EDGE_RISING>;

		qcom,modem_glink_ssr {
			qcom,glink-channels = "glink_ssr";
			qcom,notify-edges = <&glink_adsp>;
		};
	};

	glink_adsp: adsp {
		transport = "smem";
		qcom,remote-pid = <2>;
		mboxes = <&apcs_glb 4>;
		mbox-names = "adsp_smem";
		interrupts = <GIC_SPI 348 IRQ_TYPE_EDGE_RISING>;

		qcom,modem_glink_ssr {
			qcom,glink-channels = "glink_ssr";
			qcom,notify-edges = <&glink_modem>;
		};
	};
};
+358 −18
Original line number Diff line number Diff line
@@ -11,47 +11,384 @@
 */

#include <linux/of.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <soc/qcom/subsystem_notif.h>
#include <linux/rpmsg/qcom_glink.h>
#include <linux/rpmsg.h>
#include <linux/ipc_logging.h>

#define NUM_SUBSYSTEMS 10

static struct qcom_glink *edge_infos[NUM_SUBSYSTEMS];
#define GLINK_PROBE_LOG_PAGE_CNT 4
static void *glink_ilc;

#define GLINK_INFO(x, ...)						       \
do {									       \
	if (glink_ilc)							       \
		ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)

#define GLINK_ERR(dev, x, ...)						       \
do {									       \
	dev_err(dev, "[%s]: "x, __func__, ##__VA_ARGS__);		       \
	if (glink_ilc)							       \
		ipc_log_string(glink_ilc, "[%s]: "x, __func__, ##__VA_ARGS__); \
} while (0)


#define GLINK_SSR_DO_CLEANUP	0
#define GLINK_SSR_CLEANUP_DONE	1
#define GLINK_SSR_PRIORITY	1
#define GLINK_SSR_REPLY_TIMEOUT	HZ

struct do_cleanup_msg {
	__le32 version;
	__le32 command;
	__le32 seq_num;
	__le32 name_len;
	char name[32];
};

struct cleanup_done_msg {
	__le32 version;
	__le32 response;
	__le32 seq_num;
};

struct glink_ssr_nb {
	struct list_head list;
	struct glink_ssr *ssr;
	void *ssr_register_handle;

	const char *glink_label;
	const char *ssr_label;

	struct notifier_block nb;
};

struct glink_ssr {
	struct device *dev;
	struct rpmsg_endpoint *ept;

	struct list_head notify_list;

	u32 seq_num;
	struct completion completion;
};

struct edge_info {
	struct list_head list;
	struct device *dev;
	struct device_node *node;

	const char *glink_label;
	const char *ssr_label;
	void *glink;

	int (*register_fn)(struct edge_info *);
	void (*unregister_fn)(struct edge_info *);
	struct notifier_block nb;
};
LIST_HEAD(edge_infos);

static int glink_ssr_ssr_cb(struct notifier_block *this,
			    unsigned long code, void *data)
{
	struct glink_ssr_nb *nb = container_of(this, struct glink_ssr_nb, nb);
	struct glink_ssr *ssr = nb->ssr;
	struct device *dev = ssr->dev;
	struct do_cleanup_msg msg;
	int ret;

	if (code == SUBSYS_AFTER_SHUTDOWN) {
		ssr->seq_num++;
		reinit_completion(&ssr->completion);

		memset(&msg, 0, sizeof(msg));
		msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP);
		msg.seq_num = cpu_to_le32(ssr->seq_num);
		msg.name_len = cpu_to_le32(strlen(nb->glink_label));
		strlcpy(msg.name, nb->glink_label, sizeof(msg.name));

		GLINK_INFO("%s: notify of %s seq_num:%d\n",
			   dev->parent->of_node->name, nb->glink_label,
			   ssr->seq_num);

		ret = rpmsg_send(ssr->ept, &msg, sizeof(msg));
		if (ret)
			GLINK_ERR(dev, "fail to send do cleanup to %s %d\n",
				  nb->ssr_label, ret);

		ret = wait_for_completion_timeout(&ssr->completion, HZ);
		if (!ret)
			GLINK_ERR(dev, "timeout waiting for cleanup resp\n");

	}
	return NOTIFY_DONE;
}

static int glink_ssr_callback(struct rpmsg_device *rpdev,
			      void *data, int len, void *priv, u32 addr)
{
	struct cleanup_done_msg *msg = data;
	struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);

	if (len < sizeof(*msg)) {
		GLINK_ERR(ssr->dev, "message too short\n");
		return -EINVAL;
	}

	if (le32_to_cpu(msg->version) != 0) {
		GLINK_ERR(ssr->dev, "invalid version\n");
		return -EINVAL;
	}

	if (le32_to_cpu(msg->response) != GLINK_SSR_CLEANUP_DONE)
		return 0;

	if (le32_to_cpu(msg->seq_num) != ssr->seq_num) {
		GLINK_ERR(ssr->dev, "invalid response sequence number %d\n",
			  msg->seq_num);
		return -EINVAL;
	}

	complete(&ssr->completion);

	GLINK_INFO("%s: received seq_num:%d\n", ssr->dev->parent->of_node->name,
		   le32_to_cpu(msg->seq_num));

	return 0;
}

static void glink_ssr_init_notify(struct glink_ssr *ssr)
{
	struct device *dev = ssr->dev;
	struct device_node *node;
	struct glink_ssr_nb *nb;
	void *handle;
	int ret;
	int i = 0;

	while (1) {
		node = of_parse_phandle(dev->of_node, "qcom,notify-edges", i++);
		if (!node)
			break;

		nb = devm_kzalloc(dev, sizeof(*nb), GFP_KERNEL);
		if (!nb)
			return;

		ret = of_property_read_string(node, "label", &nb->ssr_label);
		if (ret < 0)
			nb->ssr_label = node->name;

		ret = of_property_read_string(node, "qcom,glink-label",
					      &nb->glink_label);
		if (ret < 0) {
			GLINK_ERR(dev, "no qcom,glink-label for %s\n",
				  nb->ssr_label);
			continue;
		}

		nb->nb.notifier_call = glink_ssr_ssr_cb;
		nb->nb.priority = GLINK_SSR_PRIORITY;

		handle = subsys_notif_register_notifier(nb->ssr_label, &nb->nb);
		if (IS_ERR_OR_NULL(handle)) {
			GLINK_ERR(dev, "register fail for %s SSR notifier\n",
				  nb->ssr_label);
			continue;
		}

		nb->ssr = ssr;
		nb->ssr_register_handle = handle;
		list_add_tail(&nb->list, &ssr->notify_list);
	}
}

static int glink_ssr_probe(struct rpmsg_device *rpdev)
{
	struct glink_ssr *ssr;

	ssr = devm_kzalloc(&rpdev->dev, sizeof(*ssr), GFP_KERNEL);
	if (!ssr)
		return -ENOMEM;

	INIT_LIST_HEAD(&ssr->notify_list);
	init_completion(&ssr->completion);

	ssr->dev = &rpdev->dev;
	ssr->ept = rpdev->ept;

	glink_ssr_init_notify(ssr);

	dev_set_drvdata(&rpdev->dev, ssr);

	return 0;
}

static void glink_ssr_remove(struct rpmsg_device *rpdev)
{
	struct glink_ssr *ssr = dev_get_drvdata(&rpdev->dev);
	struct glink_ssr_nb *nb;

	list_for_each_entry(nb, &ssr->notify_list, list) {
		subsys_notif_unregister_notifier(nb->ssr_register_handle,
						 &nb->nb);
	}

	dev_set_drvdata(&rpdev->dev, NULL);
}

static const struct rpmsg_device_id glink_ssr_match[] = {
	{ "glink_ssr" },
	{}
};

static struct rpmsg_driver glink_ssr_driver = {
	.probe = glink_ssr_probe,
	.remove = glink_ssr_remove,
	.callback = glink_ssr_callback,
	.id_table = glink_ssr_match,
	.drv = {
		.name = "glink_ssr",
	},
};
module_rpmsg_driver(glink_ssr_driver);

static int glink_probe_ssr_cb(struct notifier_block *this,
			      unsigned long code, void *data)
{
	struct edge_info *einfo = container_of(this, struct edge_info, nb);

	GLINK_INFO("received %d for %s", code, einfo->ssr_label);

	switch (code) {
	case SUBSYS_AFTER_POWERUP:
		einfo->register_fn(einfo);
		break;
	case SUBSYS_AFTER_SHUTDOWN:
		einfo->unregister_fn(einfo);
		break;
	default:
		break;
	}
	return NOTIFY_DONE;
}

static int glink_probe_smem_reg(struct edge_info *einfo)
{
	struct device *dev = einfo->dev;

	einfo->glink = qcom_glink_smem_register(dev, einfo->node);
	if (IS_ERR_OR_NULL(einfo->glink)) {
		GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
		einfo->glink = NULL;
	}
	GLINK_INFO("register successful for %s\n", einfo->ssr_label);

	return 0;
}

static void glink_probe_smem_unreg(struct edge_info *einfo)
{
	if (einfo->glink)
		qcom_glink_smem_unregister(einfo->glink);

	einfo->glink = NULL;
	GLINK_INFO("unregister for %s\n", einfo->ssr_label);

}

static int glink_probe_spss_reg(struct edge_info *einfo)
{
	struct device *dev = einfo->dev;

	einfo->glink = qcom_glink_spss_register(dev, einfo->node);
	if (IS_ERR_OR_NULL(einfo->glink)) {
		GLINK_ERR(dev, "register failed for %s\n", einfo->ssr_label);
		einfo->glink = NULL;
	}
	GLINK_INFO("register successful for %s\n", einfo->ssr_label);

	return 0;
}

static void glink_probe_spss_unreg(struct edge_info *einfo)
{
	if (einfo->glink)
		qcom_glink_spss_unregister(einfo->glink);

	einfo->glink = NULL;
	GLINK_INFO("unregister for %s\n", einfo->ssr_label);
}

static void probe_subsystem(struct device *dev, struct device_node *np)
{
	struct qcom_glink *glink = ERR_PTR(-EINVAL);
	struct edge_info *einfo;
	const char *transport;
	u32 pid;
	void *handle;
	int ret;

	ret = of_property_read_u32(np, "qcom,remote-pid", &pid);
	if (ret || pid >= NUM_SUBSYSTEMS) {
		dev_err(dev, "invalid pid:%d ret:%d\n", pid, ret);
	einfo = devm_kzalloc(dev, sizeof(*einfo), GFP_KERNEL);
	if (!einfo)
		return;

	ret = of_property_read_string(np, "label", &einfo->ssr_label);
	if (ret < 0)
		einfo->ssr_label = np->name;

	ret = of_property_read_string(np, "qcom,glink-label",
				      &einfo->glink_label);
	if (ret < 0) {
		GLINK_ERR(dev, "no qcom,glink-label for %s\n",
			  einfo->ssr_label);
		goto free_einfo;
	}

	einfo->dev = dev;
	einfo->node = np;

	ret = of_property_read_string(np, "transport", &transport);
	if (ret < 0) {
		dev_err(dev, "missing transport pid:%d\n", pid);
		return;
		GLINK_ERR(dev, "%s missing transport\n", einfo->ssr_label);
		goto free_einfo;
	}
	if (!strcmp(transport, "smem"))
		glink = qcom_glink_smem_register(dev, np);
	else if (!strcmp(transport, "spss"))
		glink = qcom_glink_spss_register(dev, np);

	if (IS_ERR(glink)) {
		dev_err(dev, "%s failed %d\n", np->name, PTR_ERR(glink));
		return;
	if (!strcmp(transport, "smem")) {
		einfo->register_fn = glink_probe_smem_reg;
		einfo->unregister_fn = glink_probe_smem_unreg;
	} else if (!strcmp(transport, "spss")) {
		einfo->register_fn = glink_probe_spss_reg;
		einfo->unregister_fn = glink_probe_spss_unreg;
	}
	edge_infos[pid] = glink;

	einfo->nb.notifier_call = glink_probe_ssr_cb;

	handle = subsys_notif_register_notifier(einfo->ssr_label, &einfo->nb);
	if (IS_ERR_OR_NULL(handle)) {
		GLINK_ERR(dev, "could not register for SSR notifier for %s\n",
			  einfo->ssr_label);
		goto free_einfo;
	}

	list_add_tail(&einfo->list, &edge_infos);
	GLINK_INFO("probe successful for %s\n", einfo->ssr_label);

	return;

free_einfo:
	devm_kfree(dev, einfo);
	return;
}

static int glink_probe(struct platform_device *pdev)
{
	struct device_node *cn, *pn = pdev->dev.of_node;
	struct device_node *pn = pdev->dev.of_node;
	struct device_node *cn;

	for_each_available_child_of_node(pn, cn) {
		probe_subsystem(&pdev->dev, cn);
@@ -77,6 +414,9 @@ static int __init glink_probe_init(void)
{
	int ret;

	glink_ilc = ipc_log_context_create(GLINK_PROBE_LOG_PAGE_CNT,
					   "glink_probe", 0);

	ret = platform_driver_register(&glink_probe_driver);
	if (ret) {
		pr_err("%s: glink_probe register failed %d\n",