From: Alessandro Carminati This patch introduces a new series of tests for devmem. Test cases are mapped against the tested Function's expectations defined in /drivers/char/mem.c. Signed-off-by: Alessandro Carminati --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/devmem/Makefile | 13 + tools/testing/selftests/devmem/debug.c | 25 + tools/testing/selftests/devmem/debug.h | 14 + tools/testing/selftests/devmem/devmem.c | 200 ++++++++ tools/testing/selftests/devmem/ram_map.c | 250 ++++++++++ tools/testing/selftests/devmem/ram_map.h | 38 ++ tools/testing/selftests/devmem/secret.c | 46 ++ tools/testing/selftests/devmem/secret.h | 13 + tools/testing/selftests/devmem/tests.c | 569 +++++++++++++++++++++++ tools/testing/selftests/devmem/tests.h | 45 ++ tools/testing/selftests/devmem/utils.c | 379 +++++++++++++++ tools/testing/selftests/devmem/utils.h | 119 +++++ 13 files changed, 1712 insertions(+) create mode 100644 tools/testing/selftests/devmem/Makefile create mode 100644 tools/testing/selftests/devmem/debug.c create mode 100644 tools/testing/selftests/devmem/debug.h create mode 100644 tools/testing/selftests/devmem/devmem.c create mode 100644 tools/testing/selftests/devmem/ram_map.c create mode 100644 tools/testing/selftests/devmem/ram_map.h create mode 100644 tools/testing/selftests/devmem/secret.c create mode 100644 tools/testing/selftests/devmem/secret.h create mode 100644 tools/testing/selftests/devmem/tests.c create mode 100644 tools/testing/selftests/devmem/tests.h create mode 100644 tools/testing/selftests/devmem/utils.c create mode 100644 tools/testing/selftests/devmem/utils.h diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 030da61dbff3..55d228572e37 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -16,6 +16,7 @@ TARGETS += cpu-hotplug TARGETS += damon TARGETS += devices/error_logs TARGETS += devices/probe +TARGETS += devmem/devmem TARGETS += dmabuf-heaps TARGETS += drivers/dma-buf TARGETS += drivers/ntsync diff --git a/tools/testing/selftests/devmem/Makefile b/tools/testing/selftests/devmem/Makefile new file mode 100644 index 000000000000..8aca8cbe2957 --- /dev/null +++ b/tools/testing/selftests/devmem/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# Kselftest Makefile for devmem test + +CFLAGS += -Wall -O2 + +TEST_GEN_PROGS_EXTENDED := devmem + +$(OUTPUT)/devmem: devmem.c $(OUTPUT)/ram_map.o $(OUTPUT)/secret.o $(OUTPUT)/tests.o $(OUTPUT)/utils.o $(OUTPUT)/debug.o + $(CC) $^ -o $@ $(CFLAGS) + +EXTRA_CLEAN += $(OUTPUT)/ram_map.o $(OUTPUT)/secret.o $(OUTPUT)/tests.o $(OUTPUT)/utils.o $(OUTPUT)/debug.o + +include ../lib.mk diff --git a/tools/testing/selftests/devmem/debug.c b/tools/testing/selftests/devmem/debug.c new file mode 100644 index 000000000000..db88a760414d --- /dev/null +++ b/tools/testing/selftests/devmem/debug.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * devmem test debug.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include + +#define DEBUG_FLAG 0 +int pdebug = DEBUG_FLAG; + +void deb_printf(const char *fmt, ...) +{ + va_list args; + + if (pdebug) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + } +} + diff --git a/tools/testing/selftests/devmem/debug.h b/tools/testing/selftests/devmem/debug.h new file mode 100644 index 000000000000..323f44b94aaa --- /dev/null +++ b/tools/testing/selftests/devmem/debug.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * devmem test debug.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef DEBUG_H +#define DEBUG_H +extern int pdebug; +void deb_printf(const char *fmt, ...); +#endif + diff --git a/tools/testing/selftests/devmem/devmem.c b/tools/testing/selftests/devmem/devmem.c new file mode 100644 index 000000000000..1b3fa5a46f67 --- /dev/null +++ b/tools/testing/selftests/devmem/devmem.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test devmem.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "secret.h" +#include "debug.h" +#include "ram_map.h" +#include "tests.h" +#include "debug.h" +#include "../kselftest.h" + +struct char_mem_test test_set[] = { +{ + "test_devmem_access", + &test_devmem_access, + "Test whether /dev/mem is accessible - memory_open FE_1, FE_2, FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_FATAL|F_MISC_INIT_PRV +}, +{ "test_open_devnum", + &test_open_devnum, + "Test open /dev/mem provides the correct min, maj - memory_open - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ}, +{ + "test_strict_devmem", + &test_strict_devmem, + "Test Strict Devmem enabled - Dependency", + F_ARCH_ALL|F_BITS_ALL|F_MISC_STRICT_DEVMEM_PRV|F_MISC_DONT_CARE +}, +{ + "test_read_at_addr_32bit_ge", + &test_read_at_addr_32bit_ge, + "Test read 64bit ppos vs 32 bit addr - read_mem - FE_1", + F_ARCH_ALL|F_BITS_B32|F_MISC_INIT_REQ +}, +{ + "test_read_outside_linear_map", + &test_read_outside_linear_map, + "Test read outside linear map - read_mem - FE_2", + F_ARCH_ALL|F_BITS_B32|F_MISC_INIT_REQ +}, +{ + "test_read_secret_area", + &test_read_secret_area, + "Test read memfd_secret area can not being accessed - read_mem - FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_allowed_area", + &test_read_allowed_area, + "test read allowed area - read_mem - FE_5", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_allowed_area_ppos_advance", + &test_read_allowed_area_ppos_advance, + "test read allowed area increments ppos - read_mem - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_restricted_area", + &test_read_restricted_area, + "test read restricted returns zeros - read_mem - FE_6", + F_ARCH_X86|F_BITS_ALL|F_MISC_INIT_REQ|F_MISC_STRICT_DEVMEM_REQ +}, +{ + "test_write_outside_area", + &test_write_outside_area, + "test write outside - write_mem - FE_2", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ|F_MISC_WARN_ON_FAILURE +}, +{ + "test_seek_seek_set", + &test_seek_seek_set, + "test seek funcction SEEK_SET - memory_lseek - FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_seek_seek_cur", + &test_seek_seek_cur, + "test seek function SEEK_CUR - memory_lseek - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_seek_seek_other", + &test_seek_seek_other, + "test seek function SEEK_END other - memory_lseek - FE_5", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +}; + +int main(int argc, char *argv[]) +{ + int tests_skipped = 0; + int tests_failed = 0; + int tests_passed = 0; + int i, tmp_res; + struct test_context t; + char *str_res, *str_warn; + struct char_mem_test *current; + + t.srcbuf = malloc_pb(BOUNCE_BUF_SIZE); + t.dstbuf = malloc_pb(BOUNCE_BUF_SIZE); + if (!t.srcbuf || !t.dstbuf) { + printf("can't allocate buffers!\n"); + exit(-1); + } + // seet verbose flag from cmdline + t.verbose = false; + if ((argc >= 2) && (!strcmp(argv[1], "-v"))) { + t.verbose = true; + pdebug = 1; + } + + t.map = parse_iomem(); + if (!t.map) + goto exit; + + if (t.verbose) { + report_physical_memory(t.map); + dump_ram_map(t.map); + } + + for (i = 0; i < ARRAY_SIZE(test_set); i++) { + str_warn = NO_WARN_STR; + current = test_set + i; + tmp_res = test_needed(&t, current); + switch (tmp_res) { + case TEST_INCOHERENT: + deb_printf("Incoherent sequence Detected\n"); + exit(-1); + break; + case TEST_ALLOWED: + deb_printf("allowed sequence Detected\n"); + str_res = ""; + printf("%s - (%s) ", current->name, current->descr); + tmp_res = current->fn(&t); + switch (tmp_res) { + case FAIL: + str_res = DC_STR; + if (!(current->flags & F_MISC_DONT_CARE)) { + str_res = KO_STR; + tests_failed++; + } + break; + case SKIPPED: + tests_skipped++; + str_res = SKP_STR; + if (current->flags & F_MISC_WARN_ON_FAILURE) + str_warn = WARN_STR; + break; + case PASS: + str_res = DC_STR; + if (!(current->flags & F_MISC_DONT_CARE)) { + tests_passed++; + str_res = OK_STR; + } + if (current->flags & F_MISC_WARN_ON_SUCCESS) + str_warn = WARN_STR; + break; + default: + tests_failed++; + printf("corrupted data\n"); + exit(-1); + } + ksft_print_msg("%s %s\n", str_res, str_warn); + if ((tmp_res == FAIL) && + (current->flags & F_MISC_FATAL)) { + printf("fatal test failed end the chain\n"); + goto cleanup; + } + case TEST_DENIED: + deb_printf("denied sequence Detected\n"); + } + } + +cleanup: + close(t.fd); + free_ram_map(t.map); + free_pb(t.srcbuf); + free_pb(t.dstbuf); +exit: + printf("Run tests = %d (passed=%d, skipped=%d failed=%d)\n", + tests_skipped+tests_failed+tests_passed, tests_passed, + tests_skipped, tests_failed); + return tests_skipped+tests_failed; +} diff --git a/tools/testing/selftests/devmem/ram_map.c b/tools/testing/selftests/devmem/ram_map.c new file mode 100644 index 000000000000..cc8855052b75 --- /dev/null +++ b/tools/testing/selftests/devmem/ram_map.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test ram_map.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include +#include +#include +#include +#include +#include "ram_map.h" +#include "utils.h" +#include "debug.h" + +static int calculate_bits(uint64_t max_addr) +{ + uint64_t value = max_addr + 1; + int bits = 0; + + while (value > 0) { + value >>= 1; + bits++; + } + return bits; +} + +uint64_t get_highest_ram_addr(const struct ram_map *map) +{ + if (!map || map->count == 0) + return 0; + return map->regions[map->count - 1].end; +} + +static int fill_iomem_regions(FILE *fp, struct ram_map *map) +{ + char line[512]; + uint64_t start, end; + char name[256]; + size_t idx = 0; + + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " : %255[^\n]", + &start, &end, name) == 3) { + map->regions[idx].start = start; + map->regions[idx].end = end; + map->regions[idx].name = strdup(name); + if (!map->regions[idx].name) { + perror("strdup"); + return -1; + } + idx++; + } + } + return 0; +} + +static size_t count_iomem_regions(FILE *fp) +{ + char line[512]; + size_t count = 0; + uint64_t start, end; + char name[256]; + + rewind(fp); + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " : %255[^\n]", + &start, &end, name) == 3) { + count++; + } + } + rewind(fp); + return count; +} + +struct ram_map *parse_iomem(void) +{ + FILE *fp = fopen("/proc/iomem", "r"); + + if (!fp) { + perror("fopen /proc/iomem"); + return NULL; + } + + size_t count = count_iomem_regions(fp); + + if (count == 0) { + fprintf(stderr, "No parsable regions found in /proc/iomem.\n"); + fclose(fp); + return NULL; + } + + struct ram_map *map = calloc(1, sizeof(*map)); + + if (!map) { + perror("calloc map"); + fclose(fp); + return NULL; + } + + map->regions = calloc(count, sizeof(*map->regions)); + if (!map->regions) { + perror("calloc regions"); + free(map); + fclose(fp); + return NULL; + } + map->count = count; + + if (fill_iomem_regions(fp, map) < 0) { + fclose(fp); + return NULL; + } + + fclose(fp); + return map; +} + +void free_ram_map(struct ram_map *map) +{ + if (!map) + return; + + for (size_t i = 0; i < map->count; i++) + free(map->regions[i].name); + + free(map->regions); + free(map); +} + +uint64_t find_last_linear_byte(int fd, uint64_t low_start, uint64_t max_addr) +{ + uint64_t low = low_start + SAFE_OFFSET; + uint64_t high = max_addr; + uint64_t last_good = 0; + + while (low <= high) { + uint64_t mid = low + (high - low) / 2; + int ret = try_read_dev_mem(fd, mid, 0, NULL); + + if (ret > 0) { + last_good = mid; + low = mid + 1; + } else if (ret == -EFAULT) { + if (mid == 0) + break; + high = mid - 1; + } else { + deb_printf("Unexpected error at 0x%llx: %d\n", + (unsigned long long)mid, -ret); + break; + } + } + return last_good; +} + +void dump_ram_map(const struct ram_map *map) +{ + printf("Parsed RAM map (%zu regions):\n", map->count); + + for (size_t i = 0; i < map->count; i++) { + printf(" %016" SCNx64 "-%016" SCNx64 " : %s\n", + map->regions[i].start, + map->regions[i].end, + map->regions[i].name); + } +} + +void report_physical_memory(const struct ram_map *map) +{ + uint64_t highest_addr = get_highest_ram_addr(map); + + if (highest_addr == 0) { + printf("No System RAM regions detected!\n"); + return; + } + + int bits = calculate_bits(highest_addr); + + printf("Highest physical RAM address: 0x%llx\n", + (unsigned long long)highest_addr); + printf("Physical address width (installed RAM): %d bits\n", bits); +} + +uint64_t find_high_system_ram_addr(const struct ram_map *map) +{ + for (size_t i = 0; i < map->count; i++) { + if (strstr(map->regions[i].name, "System RAM") && + map->regions[i].start >= LOW_MEM_LIMIT) { + return map->regions[i].start; + } + } + return 0; +} + +uint64_t pick_restricted_address(const struct ram_map *map) +{ + if (!map || !map->regions || map->count == 0) + return 0; + + for (size_t i = 0; i < map->count; i++) { + if ((!strcmp("System RAM", map->regions[i].name)) && + (map->regions[i].start < LEGACY_MEM_START)) { + uint64_t start = map->regions[i].start; + uint64_t end = map->regions[i].end; + + if (end > start) + return start + (end - start) / 2; + } + } + + return 0; +} + +uint64_t pick_outside_address(const struct ram_map *map) +{ + uint64_t max_addr = 0; + + if (!map || !map->regions || map->count == 0) + return 0; + + for (size_t i = 0; i < map->count; i++) { + if (max_addr < map->regions[i].end) + max_addr = map->regions[i].end; + } + + return max_addr + 0x1000; +} + +uint64_t pick_valid_ram_address(const struct ram_map *map) +{ + uint64_t best_low = 0, best_size = 0; + + if (!map || !map->regions || map->count == 0) + return 0; + + for (size_t i = 0; i < map->count; i++) { + if (!strcmp("System RAM", map->regions[i].name)) { + if (best_size < map->regions[i].end - + map->regions[i].start) { + best_low = map->regions[i].end; + best_size = map->regions[i].end - + map->regions[i].start; + } + } + } + return best_low + (best_size / 2); +} diff --git a/tools/testing/selftests/devmem/ram_map.h b/tools/testing/selftests/devmem/ram_map.h new file mode 100644 index 000000000000..8b1bd976b0b9 --- /dev/null +++ b/tools/testing/selftests/devmem/ram_map.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test ram_map.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef RAM_MAP_H +#define RAM_MAP_H + +#define _GNU_SOURCE +#define SAFE_OFFSET (512ULL * 1024ULL) +#define LOW_MEM_LIMIT 0x100000ULL +#define LEGACY_MEM_START 0x10000 + +struct ram_region { + uint64_t start; + uint64_t end; + char *name; +}; + +struct ram_map { + struct ram_region *regions; + size_t count; +}; + +uint64_t get_highest_ram_addr(const struct ram_map *map); +struct ram_map *parse_iomem(void); +void free_ram_map(struct ram_map *map); +uint64_t find_last_linear_byte(int fd, uint64_t low_start, uint64_t max_addr); +void dump_ram_map(const struct ram_map *map); +void report_physical_memory(const struct ram_map *map); +uint64_t find_high_system_ram_addr(const struct ram_map *map); +uint64_t pick_restricted_address(const struct ram_map *map); +uint64_t pick_outside_address(const struct ram_map *map); +uint64_t pick_valid_ram_address(const struct ram_map *map); + +#endif diff --git a/tools/testing/selftests/devmem/secret.c b/tools/testing/selftests/devmem/secret.c new file mode 100644 index 000000000000..164f58947af5 --- /dev/null +++ b/tools/testing/selftests/devmem/secret.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test secret.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include +#include + + +static int memfd_secret(unsigned int flags) +{ + return syscall(SYS_memfd_secret, flags); +} + +void *secret_alloc(size_t size) +{ + int fd = -1; + void *m; + void *result = NULL; + + fd = memfd_secret(0); + if (fd < 0) + goto out; + + if (ftruncate(fd, size) < 0) + goto out; + + m = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (m == MAP_FAILED) + goto out; + + result = m; + +out: + if (fd >= 0) + close(fd); + return result; +} + +void secret_free(void *p, size_t size) +{ + munmap(p, size); +} diff --git a/tools/testing/selftests/devmem/secret.h b/tools/testing/selftests/devmem/secret.h new file mode 100644 index 000000000000..07d263fc05e3 --- /dev/null +++ b/tools/testing/selftests/devmem/secret.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test secret.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef SECRET_H +#define SECRET_H + +void *secret_alloc(size_t size); +void secret_free(void *p, size_t size); +#endif diff --git a/tools/testing/selftests/devmem/tests.c b/tools/testing/selftests/devmem/tests.c new file mode 100644 index 000000000000..58ad673c438f --- /dev/null +++ b/tools/testing/selftests/devmem/tests.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test tests.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tests.h" +#include "debug.h" +#include "utils.h" +#include "ram_map.h" +#include "secret.h" + +#define KPROBE_EVENTS_PATH "%s/kprobe_events" +#define KPROBE_EVENTS_ENABLE "%s/events/kprobes/enable" +#define TRACE_PIPE_PATH "%s/trace_pipe" +#define MAX_LINE_LENGTH 256 +#define RETPROBE_NAME "open_retprobe" + +struct open_res { + int open_resv; + bool test_res; +}; +char *tracing_dir; +char *tracingdirs[3] = { + NULL, + "/sys/kernel/tracing", + "/sys/kernel/debug/tracing" +}; + +int check_and_set_tracefs_mount(void) +{ + FILE *mounts_file; + char line[256]; + char device[64], mount_point[128], fs_type[32]; + int retval = 0; + + mounts_file = fopen("/proc/mounts", "r"); + if (mounts_file == NULL) { + perror("Failed to open /proc/mounts"); + return 0; // Cannot verify, assume not mounted + } + + while (fgets(line, sizeof(line), mounts_file)) { + if (sscanf(line, "%s %s %s", device, mount_point, fs_type) >= 3) { + if (strcmp(mount_point, "/sys/kernel/tracing") == 0 && + strcmp(fs_type, "tracefs") == 0) { + retval = 1; + break; + } + if (strcmp(mount_point, "/sys/kernel/debug/tracing") == 0 && + strcmp(fs_type, "tracefs") == 0) { + retval = 2; + break; + } + } + } + tracing_dir = tracingdirs[retval]; + return retval; +} + +int get_device_numbers(int fd, unsigned int *major_num, + unsigned int *minor_num) +{ + struct stat file_stat; + + if (fstat(fd, &file_stat) == -1) { + perror("fstat failed"); + return -1; + } + + if (S_ISCHR(file_stat.st_mode) || S_ISBLK(file_stat.st_mode)) { + *major_num = major(file_stat.st_rdev); + *minor_num = minor(file_stat.st_rdev); + return 0; + } + fprintf(stderr, "File descriptor does not refer to a device file.\n"); + return -1; +} + +static int write_file(const char *path, const char *data) +{ + int fd = open(path, O_WRONLY | O_TRUNC); + ssize_t ret; + + if (fd < 0) { + deb_printf("Error opening file %s: %s\n", + path, strerror(errno)); + return -1; + } + deb_printf("echo \"%s\" >%s\n", data, path); + ret = write(fd, data, strlen(data)); + close(fd); + if (ret < 0) { + deb_printf("Error writing to file %s: %s\n", + path, strerror(errno)); + return -1; + } + return 0; +} + +static void cleanup_probes(void) +{ + deb_printf("Cleaning up kprobes and tracing...\n"); + char buf[100]; + + sprintf(buf, KPROBE_EVENTS_PATH, tracing_dir); + if (write_file(buf, "\n") != 0) + deb_printf("Failed to clear retprobes. Manual cleanup may be required.\n"); + + sprintf(buf, KPROBE_EVENTS_ENABLE, tracing_dir); + if (write_file(buf, "0") != 0) + deb_printf("Failed to clear retprobes. Manual cleanup may be required.\n"); + +} + +static void traced_open(const char *filename, const char *expected_func_name, + struct open_res *r) +{ + pid_t child_pid, parent_pid, traced_pid, result; + char retprobe_setup_cmd[MAX_LINE_LENGTH]; + char tmp_path[MAX_LINE_LENGTH]; + char line[MAX_LINE_LENGTH]; + int open_resv, retval = -1; + struct open_res res; + int status, timeout; + FILE *trace_file; + time_t start; + int pfd[2]; + int sn; + + r->open_resv = -1; + r->test_res = false; + + parent_pid = getpid(); + + if (pipe(pfd) == -1) { + perror("pipe failed"); + return; + } + + deb_printf("Configuring kprobes on '%s'...\n", expected_func_name); + snprintf(tmp_path, sizeof(tmp_path), KPROBE_EVENTS_PATH, tracing_dir); + snprintf(retprobe_setup_cmd, sizeof(retprobe_setup_cmd), + "r2:kprobes/%s_ret %s retval=$retval ", RETPROBE_NAME, + expected_func_name); + if (write_file(tmp_path, retprobe_setup_cmd) != 0) { + cleanup_probes(); + return; + } + snprintf(tmp_path, sizeof(tmp_path), KPROBE_EVENTS_ENABLE, + tracing_dir); + if (write_file(tmp_path, "1") != 0) { + cleanup_probes(); + return; + } + + child_pid = fork(); + if (child_pid == -1) { + deb_printf("fork failed\n"); + cleanup_probes(); + return; + } + + if (child_pid == 0) { + close(pfd[0]); + snprintf(line, sizeof(line), TRACE_PIPE_PATH, tracing_dir); + trace_file = fopen(line, "r"); + if (!trace_file) { + deb_printf("fopen trace_pipe failed in child\n"); + exit(EXIT_FAILURE); + } + + open_resv = -1; + + sleep(2); + while (fgets(line, sizeof(line), trace_file) != NULL) { + traced_pid = -1; + deb_printf("Received =>%s\n", line); + deb_printf("matching against: RETPROBE_NAME=\"%s\" and expected_func_name=\"%s\"\n", + RETPROBE_NAME, expected_func_name); + deb_printf("matching against: RETPROBE_NAME=\"%s\" => %p\n", + RETPROBE_NAME, strstr(line, RETPROBE_NAME)); + deb_printf("matching against: expected_func_name=\"%s\" =>%p\n", + expected_func_name, strstr(line, expected_func_name)); + + if (strstr(line, RETPROBE_NAME) && + strstr(line, expected_func_name)) { + sn = sscanf(line, " %*[^-]-%d%*[^=]=%x", &traced_pid, &open_resv); + deb_printf("scanned (%d)traced_pid=%d, open_resv=%d parent_pid=%d\n", + sn, traced_pid, open_resv, parent_pid); + if (traced_pid == parent_pid && open_resv == 0) { + deb_printf("found!\n"); + res.open_resv = open_resv; + res.test_res = true; + write(pfd[1], &res, sizeof(res)); + fclose(trace_file); + exit(EXIT_SUCCESS); + } + } + } + fclose(trace_file); + res.open_resv = -1; + res.test_res = false; + write(pfd[1], &res, sizeof(res)); + exit(EXIT_FAILURE); + } else { + close(pfd[1]); + sleep(1); + deb_printf("Parent process (PID %d) is calling open()...\n", + parent_pid); + retval = open(filename, O_RDONLY); + if (retval == -1) { + deb_printf("open failed\n"); + kill(child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + cleanup_probes(); + return; + } + + start = time(NULL); + timeout = 15; + + while (1) { + result = waitpid(-1, &status, WNOHANG); + if (result == -1) { + perror("waitpid"); + break; + } else if (result > 0) { + deb_printf("Child exited normally\n"); + break; + } + + if (time(NULL) - start >= timeout) { + printf("Timeout reached! Killing child...\n"); + kill(child_pid, SIGKILL); + waitpid(child_pid, NULL, 0); + break; + } + usleep(100000); + } + + if (read(pfd[0], r, sizeof(struct open_res)) != + sizeof(struct open_res)) { + deb_printf("Failed to read data from child process.\n"); + r->test_res = false; + } + + close(pfd[0]); + + cleanup_probes(); + + r->open_resv = retval; + if (r->open_resv >= 0 && r->test_res) + r->test_res = true; + else + r->test_res = false; + } +} + +int test_read_at_addr_32bit_ge(struct test_context *t) +{ + if (is_64bit_arch()) { + deb_printf("Skipped (64-bit architecture)\n"); + return SKIPPED; + } + + uint64_t target_addr = 0x100000000ULL; + int ret = try_read_dev_mem(t->fd, target_addr, 0, NULL); + + if (ret == 0) { + deb_printf("PASS: Read beyond 4 GiB at 0x%llx returned 0 bytes\n", + target_addr); + return PASS; + } + deb_printf("FAIL: Expected 0 bytes at 0x%llx, got %d (errno=%d)\n", + target_addr, ret, -ret); + return FAIL; +} + +int test_read_outside_linear_map(struct test_context *t) +{ + uint64_t tolerance, start_addr, max_addr, last_linear; + + if (sizeof(void *) == 8) { + deb_printf("Skipped: 64-bit architecture\n"); + return SKIPPED; + } + + if (!t->map || t->map->count == 0) { + deb_printf("No memory map provided!\n"); + return SKIPPED; + } + + start_addr = t->map->regions[0].start; + max_addr = t->map->regions[t->map->count - 1].end; + + deb_printf("Scanning between 0x%llx and 0x%llx\n", + (unsigned long long)start_addr, (unsigned long long)max_addr); + + last_linear = find_last_linear_byte(t->fd, start_addr, max_addr); + + deb_printf("Last readable linear address: 0x%llx\n", + (unsigned long long)last_linear); + + tolerance = 16 * 1024 * 1024; + if (last_linear + 1 >= EXPECTED_LINEAR_LIMIT - tolerance && + last_linear + 1 <= EXPECTED_LINEAR_LIMIT + tolerance) { + deb_printf("PASS: Linear map ends near 1 GiB boundary.\n"); + return PASS; + } + deb_printf("FAIL: Linear map ends unexpectedly (expected ~890MB).\n"); + return FAIL; +} + +int test_write_outside_linear_map(struct test_context *t) +{ + uint64_t tolerance, start_addr, max_addr, last_linear; + + if (sizeof(void *) == 8) { + deb_printf("Skipped: 64-bit architecture\n"); + return SKIPPED; + } + + if (!t->map || t->map->count == 0) { + deb_printf("No memory map provided!\n"); + return SKIPPED; + } + + start_addr = t->map->regions[0].start; + max_addr = t->map->regions[t->map->count - 1].end; + + deb_printf("Scanning between 0x%llx and 0x%llx\n", (unsigned long long)start_addr, + (unsigned long long)max_addr); + + last_linear = find_last_linear_byte(t->fd, start_addr, max_addr); + + deb_printf("Last readable linear address: 0x%llx\n", + (unsigned long long)last_linear); + + tolerance = 16 * 1024 * 1024; + if (last_linear + 1 >= EXPECTED_LINEAR_LIMIT - tolerance && + last_linear + 1 <= EXPECTED_LINEAR_LIMIT + tolerance) { + deb_printf("PASS: Linear map ends near 1 GiB boundary.\n"); + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + if (try_write_dev_mem(t->fd, last_linear + 0x1000, + BOUNCE_BUF_SIZE, t->srcbuf) < 0) { + return FAIL; + } + return PASS; + } + deb_printf("FAIL: Linear map ends unexpectedly (expected ~890MB).\n"); + return FAIL; +} + +int test_strict_devmem(struct test_context *t) +{ + int res = FAIL; + uint64_t addr; + ssize_t ret; + uint8_t buf; + + addr = find_high_system_ram_addr(t->map); + if (addr == 0) { + deb_printf("No high System RAM region found.\n"); + res = SKIPPED; + return res; + } + + deb_printf("Testing physical address: 0x%llx\n", addr); + + ret = pread(t->fd, &buf, 1, addr); + if (ret < 0) { + if (errno == EPERM) { + deb_printf("CONFIG_STRICT_DEVMEM is ENABLED\n"); + } else if (errno == EFAULT || errno == ENXIO) { + deb_printf("Invalid address (errno=%d). Try another region.\n", errno); + res = SKIPPED; + } else if (errno == EACCES) { + deb_printf("Access blocked by LSM or lockdown (errno=EACCES).\n"); + res = SKIPPED; + } else { + perror("pread"); + } + } else { + deb_printf("CONFIG_STRICT_DEVMEM is DISABLED\n"); + res = PASS; + } + + if (res != PASS) + t->strict_devmem_state = true; + + return res; +} + +int test_devmem_access(struct test_context *t) +{ + struct open_res res; + + if (!check_and_set_tracefs_mount()) { + deb_printf("Tracing directory not found. This test requires debugfs mounted.\n"); + return FAIL; + } + + traced_open("/dev/mem", "memory_open", &res); + if ((res.test_res) && (res.open_resv >= 0)) { + deb_printf("test_res=%d, open_resv=%d\n", + res.test_res, res.open_resv); + t->fd = res.open_resv; + t->devmem_init_state = true; + return PASS; + } + return FAIL; +} + +int test_read_secret_area(struct test_context *t) +{ + void *tmp_ptr; + + deb_printf("\ntest_read_secret_area - start\n"); + tmp_ptr = secret_alloc(BOUNCE_BUF_SIZE); + + if (tmp_ptr) { + deb_printf("secret_alloc [ok] tmp_ptr va addr = 0x%lx\n", + tmp_ptr); + fill_random_chars(tmp_ptr, BOUNCE_BUF_SIZE); // lazy alloc + if (t->verbose) + print_hex(tmp_ptr, 32); + t->tst_addr = virt_to_phys(tmp_ptr); + if (t->tst_addr) { + deb_printf("filled with things -> tst_addr phy addr = 0x%lx\n", + t->tst_addr); + if (try_read_dev_mem(t->fd, t->tst_addr, + BOUNCE_BUF_SIZE, t->dstbuf) < 0) + return PASS; + } + } + return FAIL; +} + +int test_read_restricted_area(struct test_context *t) +{ + fill_random_chars(t->dstbuf, BOUNCE_BUF_SIZE); + if (t->verbose) + print_hex(t->dstbuf, 32); + t->tst_addr = pick_restricted_address(t->map); + if (t->tst_addr) { + if (try_read_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->dstbuf) >= 0) { + if (t->verbose) + print_hex(t->dstbuf, 32); + + if (is_zero(t->dstbuf, BOUNCE_BUF_SIZE)) + return PASS; + + } + } + return FAIL; +} + +int test_read_allowed_area(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + t->tst_addr = virt_to_phys(t->srcbuf); + if (t->tst_addr) { + if (try_read_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->dstbuf) >= 0) { + deb_printf("Read OK compare twos\n", t->tst_addr); + if (t->verbose) { + print_hex(t->srcbuf, BOUNCE_BUF_SIZE); + print_hex(t->dstbuf, BOUNCE_BUF_SIZE); + } + if (!memcmp(t->srcbuf, t->dstbuf, BOUNCE_BUF_SIZE)) + return PASS; + } + } + return FAIL; +} + +int test_read_allowed_area_ppos_advance(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + memset(t->dstbuf, 0, BOUNCE_BUF_SIZE); + if (t->verbose) + print_hex(t->srcbuf, 32); + t->tst_addr = virt_to_phys(t->srcbuf); + if (t->tst_addr) { + if ((try_read_dev_mem(t->fd, t->tst_addr, + BOUNCE_BUF_SIZE / 2, t->dstbuf) >= 0) && + (try_read_inplace(t->fd, BOUNCE_BUF_SIZE / 2, + t->dstbuf) >= 0)){ + if (t->verbose) + print_hex(t->dstbuf, 32); + + if (!memcmp(t->srcbuf + BOUNCE_BUF_SIZE / 2, + t->dstbuf, BOUNCE_BUF_SIZE / 2)) { + return PASS; + } + } + } + return FAIL; +} + +int test_write_outside_area(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + t->tst_addr = pick_outside_address(t->map); + if (try_write_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->srcbuf) < 0) + return PASS; + + return FAIL; +} + +/* + * this test needs to follow test_seek_seek_set + */ +int test_seek_seek_cur(struct test_context *t) +{ + t->tst_addr = pick_valid_ram_address(t->map); + if (lseek(t->fd, 0, SEEK_SET) == (off_t)-1) + return FAIL; + + if (lseek(t->fd, t->tst_addr, SEEK_CUR) == (off_t)-1) + return FAIL; + + return PASS; +} + +int test_seek_seek_set(struct test_context *t) +{ + t->tst_addr = pick_valid_ram_address(t->map); + if (lseek(t->fd, t->tst_addr, SEEK_SET) == (off_t)-1) + return FAIL; + + return PASS; +} + +int test_seek_seek_other(struct test_context *t) +{ + if (lseek(t->fd, 0, SEEK_END) == (off_t)-1) + return PASS; + + return FAIL; +} + +int test_open_devnum(struct test_context *t) +{ + unsigned int major_num, minor_num; + + if (get_device_numbers(t->fd, &major_num, &minor_num) == 0) { + if ((major_num == 1) && (minor_num == 1)) + return PASS; + } + return FAIL; +} diff --git a/tools/testing/selftests/devmem/tests.h b/tools/testing/selftests/devmem/tests.h new file mode 100644 index 000000000000..376412034cde --- /dev/null +++ b/tools/testing/selftests/devmem/tests.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test tests.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef TESTS_H +#define TESTS_H + +#include "utils.h" + +#define EXPECTED_LINEAR_LIMIT 0x377fe000 +#define PASS 0 +#define FAIL -1 +#define SKIPPED 1 +#define OK_STR "[\e[1;32mPASS\e[0m]" +#define KO_STR "[\e[1;31mFAIL\e[0m]" +#define SKP_STR "[\e[1;33mSKIP\e[0m]" +#define DC_STR "[\e[1;33mDON'T CARE\e[0m]" +#define WARN_STR "\e[1;31mThis shouldn't have happen. Memory is probably corrupted!\e[0m" +#define NO_WARN_STR "" + +int test_read_at_addr_32bit_ge(struct test_context *t); +int test_read_outside_linear_map(struct test_context *t); +int test_strict_devmem(struct test_context *t); +int test_devmem_access(struct test_context *t); +int test_read_secret_area(struct test_context *t); +int test_read_allowed_area(struct test_context *t); +int test_read_reserved_area(struct test_context *t); +int test_read_allowed_area(struct test_context *t); +int test_read_allowed_area_ppos_advance(struct test_context *t); +int test_read_restricted_area(struct test_context *t); +int test_write_outside_area(struct test_context *t); +int test_seek_seek_cur(struct test_context *t); +int test_seek_seek_set(struct test_context *t); +int test_seek_seek_other(struct test_context *t); +int test_open_devnum(struct test_context *t); + +static inline bool is_64bit_arch(void) +{ + return sizeof(void *) == 8; +} + +#endif diff --git a/tools/testing/selftests/devmem/utils.c b/tools/testing/selftests/devmem/utils.c new file mode 100644 index 000000000000..d14e86dbd81a --- /dev/null +++ b/tools/testing/selftests/devmem/utils.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test utils.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "debug.h" + + +static inline uint64_t get_page_size(void) +{ + return (uint64_t)sysconf(_SC_PAGE_SIZE); +} + +uint64_t virt_to_phys(void *virt_addr) +{ + uint64_t virt_pfn, page_size, phys_addr, pfn; + uintptr_t virt = (uintptr_t)virt_addr; + ssize_t bytes_read; + uint64_t entry = 0; + off_t offset; + int fd; + + page_size = get_page_size(); + virt_pfn = virt / page_size; + deb_printf("page_size=%d, virt_pfn=%lu\n", page_size, virt_pfn); + + fd = open("/proc/self/pagemap", O_RDONLY); + if (fd < 0) { + deb_printf("Error opening /proc/self/pagemap: %s\n", + strerror(errno)); + return 0; + } + + offset = (off_t)(virt_pfn * sizeof(uint64_t)); + deb_printf("lseek(%d, 0x%llx, SEEK_SET)\n", fd, offset); + if (lseek(fd, offset, SEEK_SET) == (off_t)-1) { + deb_printf("Error seeking pagemap: %s\n", strerror(errno)); + close(fd); + return 0; + } + + bytes_read = read(fd, &entry, sizeof(entry)); + close(fd); + if (bytes_read != sizeof(entry)) { + deb_printf("Error reading pagemap: %s\n", strerror(errno)); + return 0; + } + + if (!(entry & (1ULL << 63))) { + deb_printf("Page not present in RAM (maybe swapped out).\n"); + return 0; + } + + pfn = entry & ((1ULL << 55) - 1); + deb_printf("entry=%llx, pfn=%llx\n", entry, pfn); + if (pfn == 0) { + deb_printf("PFN is 0 - invalid mapping.\n"); + return 0; + } + + phys_addr = (pfn * page_size) + (virt % page_size); + deb_printf("phys_addr=%llx\n", phys_addr); + return phys_addr; +} + +int try_read_inplace(int fd, int scnt, void *sbuf) +{ + ssize_t r; + + r = read(fd, sbuf, scnt); + deb_printf("read(%d, %p, %d)=%d(%d)\n", fd, sbuf, scnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int try_read_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf) +{ + int space; + ssize_t r; + void *buf; + int cnt; + + buf = sbuf ? sbuf : &space; + cnt = sbuf ? scnt : sizeof(space); + deb_printf("buf = %p, cnt = %d\n", buf, cnt); + if (lseek(fd, (off_t)addr, SEEK_SET) == (off_t)-1) + return -errno; + + deb_printf("lseek(%d, %llx, SEEK_SET)=%d\n", fd, addr, -errno); + + r = read(fd, buf, cnt); + deb_printf("read(%d, %p, %d)=%d(%d)\n", fd, buf, cnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int try_write_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf) +{ + int space; + ssize_t r; + void *buf; + int cnt; + + buf = sbuf ? sbuf : &space; + cnt = sbuf ? scnt : sizeof(space); + deb_printf("buf = %p, cnt = %d\n", buf, cnt); + if (lseek(fd, (off_t)addr, SEEK_SET) == (off_t)-1) + return -errno; + + deb_printf("lseek(%d, %llx, SEEK_SET)=%d\n", fd, addr, -errno); + + r = write(fd, buf, cnt); + deb_printf("write(%d, %p, %d)=%d(%d)\n", fd, buf, cnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int fill_random_chars(char *buf, int cnt) +{ + int bytes_read, fd; + ssize_t res; + + if (!buf || cnt <= 0) { + errno = EINVAL; + return -1; + } + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + perror("open /dev/urandom"); + return -1; + } + + bytes_read = 0; + while (bytes_read < cnt) { + res = read(fd, buf + bytes_read, cnt - bytes_read); + if (res < 0) { + if (errno == EINTR) + continue; + perror("read /dev/urandom"); + close(fd); + return -1; + } + bytes_read += res; + } + close(fd); + + return 0; +} + +bool is_zero(const void *p, size_t cnt) +{ + const char *byte_ptr = (const char *)p; + + for (size_t i = 0; i < cnt; ++i) { + if (byte_ptr[i] != 0) + return false; + } + return true; +} + +void print_hex(const void *p, size_t cnt) +{ + const unsigned char *bytes = (const unsigned char *)p; + int remainder; + size_t i; + + for (i = 0; i < cnt; i++) { + if (i % 16 == 0) { + if (i > 0) + printf("\n"); + + printf("%08lX: ", (unsigned long)(bytes + i)); + } + printf("%02X ", bytes[i]); + } + + remainder = cnt % 16; + if (remainder != 0) { + for (int j = 0; j < 16 - remainder; j++) + printf(" "); + } + + printf("\n"); +} + +static bool machine_is_compatible(unsigned int flags) +{ + unsigned int current_arch_flag = 0; + unsigned int current_bits_flag = 0; + +#if defined(__x86_64__) || defined(__i386__) + current_arch_flag = F_ARCH_X86; +#elif defined(__arm__) || defined(__aarch64__) + current_arch_flag = F_ARCH_ARM; +#elif defined(__PPC__) || defined(__powerpc__) + current_arch_flag = F_ARCH_PPC; +#elif defined(__mips__) + current_arch_flag = F_ARCH_MIPS; +#elif defined(__s390__) + current_arch_flag = F_ARCH_S390; +#elif defined(__riscv) + current_arch_flag = F_ARCH_RISCV; +#else + current_arch_flag = 0; +#endif + + if (sizeof(void *) == 8) + current_bits_flag = F_BITS_B64; + else + current_bits_flag = F_BITS_B32; + + bool arch_matches = (flags & F_ARCH_ALL) || (flags & current_arch_flag); + + bool bits_matches = (flags & F_BITS_ALL) || (flags & current_bits_flag); + + return arch_matches && bits_matches; +} + +static void print_flags(uint32_t flags) +{ + printf("Flags: 0x%08X ->", flags); + + // Architecture flags + printf(" Architecture: "); + if (flags & F_ARCH_ALL) + printf("ALL "); + + if (flags & F_ARCH_X86) + printf("X86 "); + + if (flags & F_ARCH_ARM) + printf("ARM "); + + if (flags & F_ARCH_PPC) + printf("PPC "); + + if (flags & F_ARCH_MIPS) + printf("MIPS "); + + if (flags & F_ARCH_S390) + printf("S390 "); + + if (flags & F_ARCH_RISCV) + printf("RISC-V "); + + // Bitness flags + printf(" Bitness: "); + if (flags & F_BITS_ALL) + printf("ALL "); + + if (flags & F_BITS_B64) + printf("64-bit "); + + if (flags & F_BITS_B32) + printf("32-bit "); + + // Miscellaneous flags + printf(" Miscellaneous:"); + if (flags & F_MISC_FATAL) + printf(" - F_MISC_FATAL: true"); + + if (flags & F_MISC_STRICT_DEVMEM_REQ) + printf(" - F_MISC_STRICT_DEVMEM_REQ: true"); + + if (flags & F_MISC_STRICT_DEVMEM_PRV) + printf(" - F_MISC_STRICT_DEVMEM_PRV: true"); + + if (flags & F_MISC_INIT_PRV) + printf(" - F_MISC_INIT_PRV: true"); + + if (flags & F_MISC_INIT_REQ) + printf(" - F_MISC_INIT_REQ: true"); + + printf("\n"); +} + +static void print_context(struct test_context *t) +{ + char *c; + + c = "NO"; + if (t->devmem_init_state) + c = "yes"; + printf("system state: init=%s, ", c); + c = "NO"; + if (t->strict_devmem_state) + c = "yes"; + printf("strict_devmem=%s\n", c); +} + +int test_needed(struct test_context *t, + struct char_mem_test *current) +{ + if (t->verbose) { + print_context(t); + print_flags(current->flags); + } + + if (!(t->devmem_init_state) && !(current->flags & F_MISC_INIT_PRV)) { + deb_printf("Not initialized and test does not provide initialization\n"); + return TEST_DENIED;// Not initialized and not provide init + } + if ((t->devmem_init_state) && (current->flags & F_MISC_INIT_PRV)) { + deb_printf("can not initialize again\n"); + return TEST_INCOHERENT; // can not initialize again + } + if (!(t->devmem_init_state) && (current->flags & F_MISC_INIT_PRV)) { + deb_printf("initializing: test allowed!\n"); + return TEST_ALLOWED; // initializing: test allowed! + } + if (!(t->devmem_init_state)) { + deb_printf("not initialized, can not proceed\n"); + return TEST_DENIED; // not initialized, can not proceed + } + if (!(machine_is_compatible(current->flags))) { + deb_printf("not for this architecture\n"); + return TEST_DENIED; // not for this architecture + } + if (((t->strict_devmem_state) || (current->flags & + F_MISC_STRICT_DEVMEM_REQ)) && !((t->strict_devmem_state) && + (current->flags & F_MISC_STRICT_DEVMEM_REQ))) { + deb_printf("strict_devmem requirement and offering do not meet\n"); + return TEST_DENIED;// strict_devmem requirement + } + deb_printf("test allowed!\n"); + return TEST_ALLOWED; +} + +void *malloc_pb(size_t size) +{ + if (size == 0 || size > getpagesize()) { + fprintf(stderr, "size must be greater than 0 and less than or equal to one page.\n"); + return NULL; + } + + void *ptr = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (ptr == MAP_FAILED) { + perror("mmap failed"); + return NULL; + } + + return ptr; +} + +void free_pb(void *ptr) +{ + if (ptr == NULL) + return; + + if (munmap(ptr, getpagesize()) == -1) + perror("munmap failed"); + +} diff --git a/tools/testing/selftests/devmem/utils.h b/tools/testing/selftests/devmem/utils.h new file mode 100644 index 000000000000..3a8d052f14ba --- /dev/null +++ b/tools/testing/selftests/devmem/utils.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test utils.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +#define BOUNCE_BUF_SIZE 64 +/* + * Test Case Flags: + * F_ARCH_ALL: Test valid on all HW Architectures. + * F_ARCH_X86: Test valid on x86 only. + * F_ARCH_ARM: Test valid on ARM only. + * F_ARCH_PPC: Test valid on PowerPC only. + * F_ARCH_MIPS: Test valid on MIPS only. + * F_ARCH_S390: Test valid on S390 only. + * F_ARCH_RISCV: Test valid on RISC-V only. + * + * F_BITS_ALL: Test valid on both 32b and 64b systems. + * F_BITS_B64: Test valid on 64b systems only. + * F_BITS_B32: Test valid on 32b systems only. + * + * F_MISC_FATAL: a test failure stops the execution of any other test. + * F_MISC_STRICT_DEVMEM_REQ: the test requires STRICT_DEVMEM to be defined + * in the Kernel. + * F_MISC_STRICT_DEVMEM_PRV: the test retrieves the status of STRICT_DEVMEM + * (whether it is defined or not in the Kernel). + * F_MISC_INIT_PRV: the test verify the system to be in a proper init state + * for subsequent tests to run. + * F_MISC_INIT_REQ: the test requires a proper init state as retrieved by + * F_MISC_INIT_PRV. + * F_MISC_DONT_CARE: the test is not part of the test plan, it is just + * auxiliary code that determine how to run other tests. + * F_MISC_WARN_ON_SUCCESS: This flags is applicable to negative tests. I.e. + * it raises a Warning if an operation succeeds when + * it is expected to fail. + * F_MISC_WARN_ON_FAILURE: This flags is applicable to positive tests. I.e. + * it raises a Warning if an operation fails when it + * is expected to succeed. + */ +#define F_ARCH_ALL 1 +#define F_ARCH_X86 (1 << 1) +#define F_ARCH_ARM (1 << 2) +#define F_ARCH_PPC (1 << 3) +#define F_ARCH_MIPS (1 << 4) +#define F_ARCH_S390 (1 << 5) +#define F_ARCH_RISCV (1 << 6) + +#define F_BITS_ALL (1 << 7) +#define F_BITS_B64 (1 << 8) +#define F_BITS_B32 (1 << 9) + +#define F_MISC_FATAL (1 << 10) +#define F_MISC_STRICT_DEVMEM_REQ (1 << 11) +#define F_MISC_STRICT_DEVMEM_PRV (1 << 12) +#define F_MISC_INIT_PRV (1 << 13) +#define F_MISC_INIT_REQ (1 << 14) +#define F_MISC_DONT_CARE (1 << 15) +#define F_MISC_WARN_ON_SUCCESS (1 << 16) +#define F_MISC_WARN_ON_FAILURE (1 << 17) + +enum { + TEST_DENIED, + TEST_INCOHERENT, + TEST_ALLOWED +}; + +struct test_context { + struct ram_map *map; + char *srcbuf; + char *dstbuf; + uintptr_t tst_addr; + int fd; + bool verbose; + bool strict_devmem_state; + bool devmem_init_state; +}; + +/* + * struct char_mem_test - test case structure for testing /drivers/char/mem.c + * @name: name of the test case. + * @fn: test callback implementing the test case. + * @descr: test case descriptor; it must be formatted as + * "short description"-"function-name"-"FE" + * where + * "short description" describe what the test case does, + * "function-name" is the name of the tested function in + * /drivers/char/mem.c, + * "FE" is the list of tested Function's Expectations from the + * kernel-doc header associated with "function-name". + * @flags: test case applicable flags (see list above). + */ +struct char_mem_test { + char *name; + int (*fn)(struct test_context *t); + char *descr; + uint64_t flags; +}; + +uint64_t virt_to_phys(void *virt_addr); +int try_read_inplace(int fd, int scnt, void *sbuf); +int try_read_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf); +int try_write_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf); +int fill_random_chars(char *buf, int cnt); +bool is_zero(const void *p, size_t cnt); +void print_hex(const void *p, size_t cnt); +int test_needed(struct test_context *t, struct char_mem_test *current); +void *malloc_pb(size_t size); +void free_pb(void *ptr); + +#endif + -- 2.48.1