diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 6e59b8f63e41..583e602a9b8a 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -99,6 +99,7 @@ TARGETS += pstore TARGETS += ptrace TARGETS += openat2 TARGETS += rdma +TARGETS += remoteproc TARGETS += resctrl TARGETS += riscv TARGETS += rlimits diff --git a/tools/testing/selftests/remoteproc/Makefile b/tools/testing/selftests/remoteproc/Makefile new file mode 100644 index 000000000000..deb092167526 --- /dev/null +++ b/tools/testing/selftests/remoteproc/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS += -Wall -O2 +CFLAGS += -Wno-format-truncation +CFLAGS += $(KHDR_INCLUDES) + +TEST_GEN_PROGS := remoteproc_sysfs + +include ../lib.mk diff --git a/tools/testing/selftests/remoteproc/config b/tools/testing/selftests/remoteproc/config new file mode 100644 index 000000000000..a5c237d2f3b4 --- /dev/null +++ b/tools/testing/selftests/remoteproc/config @@ -0,0 +1 @@ +CONFIG_REMOTEPROC=y diff --git a/tools/testing/selftests/remoteproc/remoteproc_helpers.h b/tools/testing/selftests/remoteproc/remoteproc_helpers.h new file mode 100644 index 000000000000..d97d4592ef72 --- /dev/null +++ b/tools/testing/selftests/remoteproc/remoteproc_helpers.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Helpers for remoteproc selftests: device enumeration and sysfs I/O. + */ +#ifndef REMOTEPROC_HELPERS_H +#define REMOTEPROC_HELPERS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define RPROC_SYSFS_CLASS "/sys/class/remoteproc" +/* Large enough for any full sysfs path including d_name (up to 255). */ +#define RPROC_PATH_MAX 512 +#define RPROC_ATTR_BUF_MAX 32 +#define RPROC_MAX_DEVS 32 + +/* + * Scan /sys/class/remoteproc/ and fill @devs with the full sysfs path of + * each remoteprocN entry (e.g. "/sys/class/remoteproc/remoteproc0"). + * Returns the number of devices found, or 0 if none. + */ +static inline int rproc_find_devices(char devs[][RPROC_PATH_MAX], int max) +{ + struct dirent *entry; + DIR *dir; + int count = 0; + + dir = opendir(RPROC_SYSFS_CLASS); + if (!dir) + return 0; + + while ((entry = readdir(dir)) && count < max) { + if (strncmp(entry->d_name, "remoteproc", 10) != 0) + continue; + snprintf(devs[count], RPROC_PATH_MAX, "%s/%s", + RPROC_SYSFS_CLASS, entry->d_name); + count++; + } + + closedir(dir); + return count; +} + +/* + * Read sysfs attribute @attr of device at sysfs path @dev into @buf. + * Strips trailing newline. Returns 0 on success, -errno on failure. + */ +static inline int rproc_sysfs_read(const char *dev, const char *attr, + char *buf, size_t len) +{ + char path[RPROC_PATH_MAX]; + ssize_t n; + int fd; + + snprintf(path, sizeof(path), "%s/%s", dev, attr); + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + n = read(fd, buf, len - 1); + close(fd); + if (n < 0) + return -errno; + + buf[n] = '\0'; + if (n > 0 && buf[n - 1] == '\n') + buf[n - 1] = '\0'; + + return 0; +} + +/* + * Write @val to sysfs attribute @attr of device at sysfs path @dev. + * Returns 0 on success, -errno on failure. + */ +static inline int rproc_sysfs_write(const char *dev, const char *attr, + const char *val) +{ + char path[RPROC_PATH_MAX]; + ssize_t n; + int fd; + + snprintf(path, sizeof(path), "%s/%s", dev, attr); + fd = open(path, O_WRONLY); + if (fd < 0) + return -errno; + + n = write(fd, val, strlen(val)); + close(fd); + if (n < 0) + return -errno; + + return 0; +} + +#endif /* REMOTEPROC_HELPERS_H */ diff --git a/tools/testing/selftests/remoteproc/remoteproc_sysfs.c b/tools/testing/selftests/remoteproc/remoteproc_sysfs.c new file mode 100644 index 000000000000..3e38b494fc25 --- /dev/null +++ b/tools/testing/selftests/remoteproc/remoteproc_sysfs.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Selftests for the remoteproc sysfs interface. + * + * Covers /sys/class/remoteproc/remoteprocN/ attributes: + * name, state, firmware, coredump, recovery + * + * All tests skip gracefully when no remoteproc devices are present. + */ +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" +#include "remoteproc_helpers.h" + +/* Valid state strings as reported by the kernel */ +static const char * const valid_states[] = { + "offline", "suspended", "running", "crashed", + "invalid", "attached", "detached", +}; + +/* Valid coredump mode strings */ +static const char * const valid_coredump[] = { + "disabled", "enabled", "inline", +}; + +static bool str_in_list(const char *str, const char * const *list, int n) +{ + int i; + + for (i = 0; i < n; i++) { + if (strcmp(str, list[i]) == 0) + return true; + } + return false; +} + +FIXTURE(sysfs) { + char dev[RPROC_PATH_MAX]; + char orig_firmware[RPROC_ATTR_BUF_MAX]; + char orig_coredump[RPROC_ATTR_BUF_MAX]; + char orig_recovery[RPROC_ATTR_BUF_MAX]; +}; + +FIXTURE_SETUP(sysfs) +{ + char devs[RPROC_MAX_DEVS][RPROC_PATH_MAX]; + int n; + + n = rproc_find_devices(devs, RPROC_MAX_DEVS); + if (n == 0) + SKIP(return, "no remoteproc devices found"); + + snprintf(self->dev, sizeof(self->dev), "%s", devs[0]); + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "firmware", + self->orig_firmware, + sizeof(self->orig_firmware))); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "coredump", + self->orig_coredump, + sizeof(self->orig_coredump))); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "recovery", + self->orig_recovery, + sizeof(self->orig_recovery))); +} + +FIXTURE_TEARDOWN(sysfs) +{ + /* Restore coredump and recovery to original values. */ + rproc_sysfs_write(self->dev, "coredump", self->orig_coredump); + rproc_sysfs_write(self->dev, "recovery", self->orig_recovery); + + /* + * Restore firmware only when device is offline; skip silently + * if the device is in any other state to avoid unintended side + * effects. + */ + char state[RPROC_ATTR_BUF_MAX]; + + if (rproc_sysfs_read(self->dev, "state", state, sizeof(state)) == 0 && + strcmp(state, "offline") == 0) + rproc_sysfs_write(self->dev, "firmware", self->orig_firmware); +} + +/* name attribute is readable and non-empty */ +TEST_F(sysfs, name_readable) +{ + char name[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "name", + name, sizeof(name))); + EXPECT_GT((int)strlen(name), 0); +} + +/* state attribute returns a known valid state string */ +TEST_F(sysfs, state_valid_value) +{ + char state[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "state", + state, sizeof(state))); + EXPECT_TRUE(str_in_list(state, valid_states, + ARRAY_SIZE(valid_states))); +} + +/* firmware attribute is readable and non-empty */ +TEST_F(sysfs, firmware_readable) +{ + char firmware[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "firmware", + firmware, sizeof(firmware))); + EXPECT_GT((int)strlen(firmware), 0); +} + +/* coredump attribute returns a known valid mode string */ +TEST_F(sysfs, coredump_valid_value) +{ + char coredump[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "coredump", + coredump, sizeof(coredump))); + EXPECT_TRUE(str_in_list(coredump, valid_coredump, + ARRAY_SIZE(valid_coredump))); +} + +/* recovery attribute returns "enabled" or "disabled" */ +TEST_F(sysfs, recovery_valid_value) +{ + char recovery[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "recovery", + recovery, sizeof(recovery))); + EXPECT_TRUE(strcmp(recovery, "enabled") == 0 || + strcmp(recovery, "disabled") == 0); +} + +/* Writing an unrecognised string to state must be rejected with EINVAL */ +TEST_F(sysfs, state_write_invalid_rejected) +{ + EXPECT_EQ(-EINVAL, rproc_sysfs_write(self->dev, "state", + "invalidcmd")); +} + +/* Writing an unrecognised string to coredump must be rejected with EINVAL */ +TEST_F(sysfs, coredump_write_invalid_rejected) +{ + EXPECT_EQ(-EINVAL, rproc_sysfs_write(self->dev, "coredump", + "invalidmode")); +} + +/* Writing an unrecognised string to recovery must be rejected with EINVAL */ +TEST_F(sysfs, recovery_write_invalid_rejected) +{ + EXPECT_EQ(-EINVAL, rproc_sysfs_write(self->dev, "recovery", + "invalidopt")); +} + +/* + * When device is offline, firmware attribute must be writable. + * Teardown restores the original firmware value. + */ +TEST_F(sysfs, firmware_write_while_offline) +{ + char state[RPROC_ATTR_BUF_MAX]; + char readback[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "state", + state, sizeof(state))); + if (strcmp(state, "offline") != 0) + SKIP(return, "device not offline, skipping firmware write test"); + + ASSERT_EQ(0, rproc_sysfs_write(self->dev, "firmware", + "test-firmware.elf")); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "firmware", + readback, sizeof(readback))); + EXPECT_EQ(0, strcmp(readback, "test-firmware.elf")); +} + +/* + * When the device is not offline, firmware attribute must be rejected + * with EBUSY; rproc_set_firmware() only permits changes in offline state. + */ +TEST_F(sysfs, firmware_write_while_running_rejected) +{ + char state[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "state", + state, sizeof(state))); + if (strcmp(state, "running") != 0) + SKIP(return, "device not running, skipping firmware busy test"); + + EXPECT_EQ(-EBUSY, rproc_sysfs_write(self->dev, "firmware", + "test-firmware.elf")); +} + +/* Cycle through all valid coredump modes and verify each reads back correctly */ +TEST_F(sysfs, coredump_roundtrip) +{ + static const char * const modes[] = { "disabled", "enabled", "inline" }; + char readback[RPROC_ATTR_BUF_MAX]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(modes); i++) { + ASSERT_EQ(0, rproc_sysfs_write(self->dev, "coredump", + modes[i])); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "coredump", + readback, sizeof(readback))); + EXPECT_EQ(0, strcmp(readback, modes[i])); + } +} + +/* Toggle recovery enabled <-> disabled and verify each reads back correctly */ +TEST_F(sysfs, recovery_roundtrip) +{ + char readback[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_write(self->dev, "recovery", "disabled")); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "recovery", + readback, sizeof(readback))); + EXPECT_EQ(0, strcmp(readback, "disabled")); + + ASSERT_EQ(0, rproc_sysfs_write(self->dev, "recovery", "enabled")); + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "recovery", + readback, sizeof(readback))); + EXPECT_EQ(0, strcmp(readback, "enabled")); +} + +/* + * Writing "stop" to a device already in offline state must be rejected + * with EINVAL; rproc_shutdown() only accepts RUNNING or ATTACHED states. + */ +TEST_F(sysfs, stop_when_offline_rejected) +{ + char state[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "state", + state, sizeof(state))); + if (strcmp(state, "offline") != 0) + SKIP(return, "device not offline"); + + EXPECT_EQ(-EINVAL, rproc_sysfs_write(self->dev, "state", "stop")); +} + +/* + * Writing "detach" to a device already in offline state must be rejected + * with EINVAL; rproc_detach() only accepts ATTACHED state. + */ +TEST_F(sysfs, detach_when_offline_rejected) +{ + char state[RPROC_ATTR_BUF_MAX]; + + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "state", + state, sizeof(state))); + if (strcmp(state, "offline") != 0) + SKIP(return, "device not offline"); + + EXPECT_EQ(-EINVAL, rproc_sysfs_write(self->dev, "state", "detach")); +} + +/* + * Writing "recover" to the recovery attribute must be accepted without + * error regardless of the current recovery flag; it triggers an immediate + * recovery attempt without changing the enabled/disabled flag. + */ +TEST_F(sysfs, recovery_recover_accepted) +{ + char recovery[RPROC_ATTR_BUF_MAX]; + + EXPECT_EQ(0, rproc_sysfs_write(self->dev, "recovery", "recover")); + + /* Flag must remain unchanged after a "recover" command. */ + ASSERT_EQ(0, rproc_sysfs_read(self->dev, "recovery", + recovery, sizeof(recovery))); + EXPECT_TRUE(strcmp(recovery, "enabled") == 0 || + strcmp(recovery, "disabled") == 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/remoteproc/settings b/tools/testing/selftests/remoteproc/settings new file mode 100644 index 000000000000..ba4d85f74cd6 --- /dev/null +++ b/tools/testing/selftests/remoteproc/settings @@ -0,0 +1 @@ +timeout=90