158 lines
3.6 KiB
C
158 lines
3.6 KiB
C
|
/*
|
||
|
* Remote Processor Framework
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <linux/remoteproc.h>
|
||
|
|
||
|
#include "remoteproc_internal.h"
|
||
|
|
||
|
#define to_rproc(d) container_of(d, struct rproc, dev)
|
||
|
|
||
|
/* Expose the loaded / running firmware name via sysfs */
|
||
|
static ssize_t firmware_show(struct device *dev, struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct rproc *rproc = to_rproc(dev);
|
||
|
|
||
|
return sprintf(buf, "%s\n", rproc->firmware);
|
||
|
}
|
||
|
|
||
|
/* Change firmware name via sysfs */
|
||
|
static ssize_t firmware_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct rproc *rproc = to_rproc(dev);
|
||
|
char *p;
|
||
|
int err, len = count;
|
||
|
|
||
|
err = mutex_lock_interruptible(&rproc->lock);
|
||
|
if (err) {
|
||
|
dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, err);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (rproc->state != RPROC_OFFLINE) {
|
||
|
dev_err(dev, "can't change firmware while running\n");
|
||
|
err = -EBUSY;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
len = strcspn(buf, "\n");
|
||
|
if (!len) {
|
||
|
dev_err(dev, "can't provide a NULL firmware\n");
|
||
|
err = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
p = kstrndup(buf, len, GFP_KERNEL);
|
||
|
if (!p) {
|
||
|
err = -ENOMEM;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
kfree(rproc->firmware);
|
||
|
rproc->firmware = p;
|
||
|
out:
|
||
|
mutex_unlock(&rproc->lock);
|
||
|
|
||
|
return err ? err : count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(firmware);
|
||
|
|
||
|
/*
|
||
|
* A state-to-string lookup table, for exposing a human readable state
|
||
|
* via sysfs. Always keep in sync with enum rproc_state
|
||
|
*/
|
||
|
static const char * const rproc_state_string[] = {
|
||
|
[RPROC_OFFLINE] = "offline",
|
||
|
[RPROC_SUSPENDED] = "suspended",
|
||
|
[RPROC_RUNNING] = "running",
|
||
|
[RPROC_CRASHED] = "crashed",
|
||
|
[RPROC_DELETED] = "deleted",
|
||
|
[RPROC_LAST] = "invalid",
|
||
|
};
|
||
|
|
||
|
/* Expose the state of the remote processor via sysfs */
|
||
|
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct rproc *rproc = to_rproc(dev);
|
||
|
unsigned int state;
|
||
|
|
||
|
state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
|
||
|
return sprintf(buf, "%s\n", rproc_state_string[state]);
|
||
|
}
|
||
|
|
||
|
/* Change remote processor state via sysfs */
|
||
|
static ssize_t state_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct rproc *rproc = to_rproc(dev);
|
||
|
int ret = 0;
|
||
|
|
||
|
if (sysfs_streq(buf, "start")) {
|
||
|
if (rproc->state == RPROC_RUNNING)
|
||
|
return -EBUSY;
|
||
|
|
||
|
ret = rproc_boot(rproc);
|
||
|
if (ret)
|
||
|
dev_err(&rproc->dev, "Boot failed: %d\n", ret);
|
||
|
} else if (sysfs_streq(buf, "stop")) {
|
||
|
if (rproc->state != RPROC_RUNNING)
|
||
|
return -EINVAL;
|
||
|
|
||
|
rproc_shutdown(rproc);
|
||
|
} else {
|
||
|
dev_err(&rproc->dev, "Unrecognised option: %s\n", buf);
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
return ret ? ret : count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(state);
|
||
|
|
||
|
static struct attribute *rproc_attrs[] = {
|
||
|
&dev_attr_firmware.attr,
|
||
|
&dev_attr_state.attr,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group rproc_devgroup = {
|
||
|
.attrs = rproc_attrs
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *rproc_devgroups[] = {
|
||
|
&rproc_devgroup,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
struct class rproc_class = {
|
||
|
.name = "remoteproc",
|
||
|
.dev_groups = rproc_devgroups,
|
||
|
};
|
||
|
|
||
|
int __init rproc_init_sysfs(void)
|
||
|
{
|
||
|
/* create remoteproc device class for sysfs */
|
||
|
int err = class_register(&rproc_class);
|
||
|
|
||
|
if (err)
|
||
|
pr_err("remoteproc: unable to register class\n");
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
void __exit rproc_exit_sysfs(void)
|
||
|
{
|
||
|
class_unregister(&rproc_class);
|
||
|
}
|