[RFC v2 PATCH 3/3] selftests/devmem: initial testset

Gabriele Paoloni posted 3 patches 6 hours ago
[RFC v2 PATCH 3/3] selftests/devmem: initial testset
Posted by Gabriele Paoloni 6 hours ago
From: Alessandro Carminati <acarmina@redhat.com>

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 <acarmina@redhat.com>
---
 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 <stdio.h>
+#include <stdarg.h>
+
+#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 <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#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 <sys/syscall.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+
+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 <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#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<i>"
+ *         where
+ *         "short description" describe what the test case does,
+ *         "function-name" is the name of the tested function in
+ *         /drivers/char/mem.c,
+ *         "FE<i>" 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