162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Remote Processor Framework
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/remoteproc.h>
762306a36Sopenharmony_ci#include <linux/slab.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include "remoteproc_internal.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define to_rproc(d) container_of(d, struct rproc, dev)
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cistatic ssize_t recovery_show(struct device *dev,
1462306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
1562306a36Sopenharmony_ci{
1662306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	return sysfs_emit(buf, "%s", rproc->recovery_disabled ? "disabled\n" : "enabled\n");
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * By writing to the 'recovery' sysfs entry, we control the behavior of the
2362306a36Sopenharmony_ci * recovery mechanism dynamically. The default value of this entry is "enabled".
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * The 'recovery' sysfs entry supports these commands:
2662306a36Sopenharmony_ci *
2762306a36Sopenharmony_ci * enabled:	When enabled, the remote processor will be automatically
2862306a36Sopenharmony_ci *		recovered whenever it crashes. Moreover, if the remote
2962306a36Sopenharmony_ci *		processor crashes while recovery is disabled, it will
3062306a36Sopenharmony_ci *		be automatically recovered too as soon as recovery is enabled.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * disabled:	When disabled, a remote processor will remain in a crashed
3362306a36Sopenharmony_ci *		state if it crashes. This is useful for debugging purposes;
3462306a36Sopenharmony_ci *		without it, debugging a crash is substantially harder.
3562306a36Sopenharmony_ci *
3662306a36Sopenharmony_ci * recover:	This function will trigger an immediate recovery if the
3762306a36Sopenharmony_ci *		remote processor is in a crashed state, without changing
3862306a36Sopenharmony_ci *		or checking the recovery state (enabled/disabled).
3962306a36Sopenharmony_ci *		This is useful during debugging sessions, when one expects
4062306a36Sopenharmony_ci *		additional crashes to happen after enabling recovery. In this
4162306a36Sopenharmony_ci *		case, enabling recovery will make it hard to debug subsequent
4262306a36Sopenharmony_ci *		crashes, so it's recommended to keep recovery disabled, and
4362306a36Sopenharmony_ci *		instead use the "recover" command as needed.
4462306a36Sopenharmony_ci */
4562306a36Sopenharmony_cistatic ssize_t recovery_store(struct device *dev,
4662306a36Sopenharmony_ci			      struct device_attribute *attr,
4762306a36Sopenharmony_ci			      const char *buf, size_t count)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (sysfs_streq(buf, "enabled")) {
5262306a36Sopenharmony_ci		/* change the flag and begin the recovery process if needed */
5362306a36Sopenharmony_ci		rproc->recovery_disabled = false;
5462306a36Sopenharmony_ci		rproc_trigger_recovery(rproc);
5562306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "disabled")) {
5662306a36Sopenharmony_ci		rproc->recovery_disabled = true;
5762306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "recover")) {
5862306a36Sopenharmony_ci		/* begin the recovery process without changing the flag */
5962306a36Sopenharmony_ci		rproc_trigger_recovery(rproc);
6062306a36Sopenharmony_ci	} else {
6162306a36Sopenharmony_ci		return -EINVAL;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return count;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(recovery);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/*
6962306a36Sopenharmony_ci * A coredump-configuration-to-string lookup table, for exposing a
7062306a36Sopenharmony_ci * human readable configuration via sysfs. Always keep in sync with
7162306a36Sopenharmony_ci * enum rproc_coredump_mechanism
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_cistatic const char * const rproc_coredump_str[] = {
7462306a36Sopenharmony_ci	[RPROC_COREDUMP_DISABLED]	= "disabled",
7562306a36Sopenharmony_ci	[RPROC_COREDUMP_ENABLED]	= "enabled",
7662306a36Sopenharmony_ci	[RPROC_COREDUMP_INLINE]		= "inline",
7762306a36Sopenharmony_ci};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/* Expose the current coredump configuration via debugfs */
8062306a36Sopenharmony_cistatic ssize_t coredump_show(struct device *dev,
8162306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return sysfs_emit(buf, "%s\n", rproc_coredump_str[rproc->dump_conf]);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * By writing to the 'coredump' sysfs entry, we control the behavior of the
9062306a36Sopenharmony_ci * coredump mechanism dynamically. The default value of this entry is "default".
9162306a36Sopenharmony_ci *
9262306a36Sopenharmony_ci * The 'coredump' sysfs entry supports these commands:
9362306a36Sopenharmony_ci *
9462306a36Sopenharmony_ci * disabled:	This is the default coredump mechanism. Recovery will proceed
9562306a36Sopenharmony_ci *		without collecting any dump.
9662306a36Sopenharmony_ci *
9762306a36Sopenharmony_ci * default:	When the remoteproc crashes the entire coredump will be
9862306a36Sopenharmony_ci *		copied to a separate buffer and exposed to userspace.
9962306a36Sopenharmony_ci *
10062306a36Sopenharmony_ci * inline:	The coredump will not be copied to a separate buffer and the
10162306a36Sopenharmony_ci *		recovery process will have to wait until data is read by
10262306a36Sopenharmony_ci *		userspace. But this avoid usage of extra memory.
10362306a36Sopenharmony_ci */
10462306a36Sopenharmony_cistatic ssize_t coredump_store(struct device *dev,
10562306a36Sopenharmony_ci			      struct device_attribute *attr,
10662306a36Sopenharmony_ci			      const char *buf, size_t count)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (rproc->state == RPROC_CRASHED) {
11162306a36Sopenharmony_ci		dev_err(&rproc->dev, "can't change coredump configuration\n");
11262306a36Sopenharmony_ci		return -EBUSY;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (sysfs_streq(buf, "disabled")) {
11662306a36Sopenharmony_ci		rproc->dump_conf = RPROC_COREDUMP_DISABLED;
11762306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "enabled")) {
11862306a36Sopenharmony_ci		rproc->dump_conf = RPROC_COREDUMP_ENABLED;
11962306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "inline")) {
12062306a36Sopenharmony_ci		rproc->dump_conf = RPROC_COREDUMP_INLINE;
12162306a36Sopenharmony_ci	} else {
12262306a36Sopenharmony_ci		dev_err(&rproc->dev, "Invalid coredump configuration\n");
12362306a36Sopenharmony_ci		return -EINVAL;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return count;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_cistatic DEVICE_ATTR_RW(coredump);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci/* Expose the loaded / running firmware name via sysfs */
13162306a36Sopenharmony_cistatic ssize_t firmware_show(struct device *dev, struct device_attribute *attr,
13262306a36Sopenharmony_ci			  char *buf)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
13562306a36Sopenharmony_ci	const char *firmware = rproc->firmware;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/*
13862306a36Sopenharmony_ci	 * If the remote processor has been started by an external
13962306a36Sopenharmony_ci	 * entity we have no idea of what image it is running.  As such
14062306a36Sopenharmony_ci	 * simply display a generic string rather then rproc->firmware.
14162306a36Sopenharmony_ci	 */
14262306a36Sopenharmony_ci	if (rproc->state == RPROC_ATTACHED)
14362306a36Sopenharmony_ci		firmware = "unknown";
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return sprintf(buf, "%s\n", firmware);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci/* Change firmware name via sysfs */
14962306a36Sopenharmony_cistatic ssize_t firmware_store(struct device *dev,
15062306a36Sopenharmony_ci			      struct device_attribute *attr,
15162306a36Sopenharmony_ci			      const char *buf, size_t count)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
15462306a36Sopenharmony_ci	int err;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	err = rproc_set_firmware(rproc, buf);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return err ? err : count;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(firmware);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/*
16362306a36Sopenharmony_ci * A state-to-string lookup table, for exposing a human readable state
16462306a36Sopenharmony_ci * via sysfs. Always keep in sync with enum rproc_state
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_cistatic const char * const rproc_state_string[] = {
16762306a36Sopenharmony_ci	[RPROC_OFFLINE]		= "offline",
16862306a36Sopenharmony_ci	[RPROC_SUSPENDED]	= "suspended",
16962306a36Sopenharmony_ci	[RPROC_RUNNING]		= "running",
17062306a36Sopenharmony_ci	[RPROC_CRASHED]		= "crashed",
17162306a36Sopenharmony_ci	[RPROC_DELETED]		= "deleted",
17262306a36Sopenharmony_ci	[RPROC_ATTACHED]	= "attached",
17362306a36Sopenharmony_ci	[RPROC_DETACHED]	= "detached",
17462306a36Sopenharmony_ci	[RPROC_LAST]		= "invalid",
17562306a36Sopenharmony_ci};
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/* Expose the state of the remote processor via sysfs */
17862306a36Sopenharmony_cistatic ssize_t state_show(struct device *dev, struct device_attribute *attr,
17962306a36Sopenharmony_ci			  char *buf)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
18262306a36Sopenharmony_ci	unsigned int state;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
18562306a36Sopenharmony_ci	return sprintf(buf, "%s\n", rproc_state_string[state]);
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci/* Change remote processor state via sysfs */
18962306a36Sopenharmony_cistatic ssize_t state_store(struct device *dev,
19062306a36Sopenharmony_ci			      struct device_attribute *attr,
19162306a36Sopenharmony_ci			      const char *buf, size_t count)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
19462306a36Sopenharmony_ci	int ret = 0;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (sysfs_streq(buf, "start")) {
19762306a36Sopenharmony_ci		ret = rproc_boot(rproc);
19862306a36Sopenharmony_ci		if (ret)
19962306a36Sopenharmony_ci			dev_err(&rproc->dev, "Boot failed: %d\n", ret);
20062306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "stop")) {
20162306a36Sopenharmony_ci		ret = rproc_shutdown(rproc);
20262306a36Sopenharmony_ci	} else if (sysfs_streq(buf, "detach")) {
20362306a36Sopenharmony_ci		ret = rproc_detach(rproc);
20462306a36Sopenharmony_ci	} else {
20562306a36Sopenharmony_ci		dev_err(&rproc->dev, "Unrecognised option: %s\n", buf);
20662306a36Sopenharmony_ci		ret = -EINVAL;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci	return ret ? ret : count;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(state);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/* Expose the name of the remote processor via sysfs */
21362306a36Sopenharmony_cistatic ssize_t name_show(struct device *dev, struct device_attribute *attr,
21462306a36Sopenharmony_ci			 char *buf)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	return sprintf(buf, "%s\n", rproc->name);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(name);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic umode_t rproc_is_visible(struct kobject *kobj, struct attribute *attr,
22362306a36Sopenharmony_ci				int n)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
22662306a36Sopenharmony_ci	struct rproc *rproc = to_rproc(dev);
22762306a36Sopenharmony_ci	umode_t mode = attr->mode;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (rproc->sysfs_read_only && (attr == &dev_attr_recovery.attr ||
23062306a36Sopenharmony_ci				       attr == &dev_attr_firmware.attr ||
23162306a36Sopenharmony_ci				       attr == &dev_attr_state.attr ||
23262306a36Sopenharmony_ci				       attr == &dev_attr_coredump.attr))
23362306a36Sopenharmony_ci		mode = 0444;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return mode;
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic struct attribute *rproc_attrs[] = {
23962306a36Sopenharmony_ci	&dev_attr_coredump.attr,
24062306a36Sopenharmony_ci	&dev_attr_recovery.attr,
24162306a36Sopenharmony_ci	&dev_attr_firmware.attr,
24262306a36Sopenharmony_ci	&dev_attr_state.attr,
24362306a36Sopenharmony_ci	&dev_attr_name.attr,
24462306a36Sopenharmony_ci	NULL
24562306a36Sopenharmony_ci};
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic const struct attribute_group rproc_devgroup = {
24862306a36Sopenharmony_ci	.attrs = rproc_attrs,
24962306a36Sopenharmony_ci	.is_visible = rproc_is_visible,
25062306a36Sopenharmony_ci};
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic const struct attribute_group *rproc_devgroups[] = {
25362306a36Sopenharmony_ci	&rproc_devgroup,
25462306a36Sopenharmony_ci	NULL
25562306a36Sopenharmony_ci};
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistruct class rproc_class = {
25862306a36Sopenharmony_ci	.name		= "remoteproc",
25962306a36Sopenharmony_ci	.dev_groups	= rproc_devgroups,
26062306a36Sopenharmony_ci};
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciint __init rproc_init_sysfs(void)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	/* create remoteproc device class for sysfs */
26562306a36Sopenharmony_ci	int err = class_register(&rproc_class);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	if (err)
26862306a36Sopenharmony_ci		pr_err("remoteproc: unable to register class\n");
26962306a36Sopenharmony_ci	return err;
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_civoid __exit rproc_exit_sysfs(void)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	class_unregister(&rproc_class);
27562306a36Sopenharmony_ci}
276