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

Gabriele Paoloni posted 3 patches 5 months ago
[RFC v2 PATCH 3/3] selftests/devmem: initial testset
Posted by Gabriele Paoloni 5 months 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
Re: [RFC v2 PATCH 3/3] selftests/devmem: initial testset
Posted by Greg KH 3 months, 3 weeks ago
On Wed, Sep 10, 2025 at 07:00:00PM +0200, Gabriele Paoloni wrote:
> 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.

Cool, but:

> 
> 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 +++++

That's a lot of files for a "simple" test.  Doesn't LTP have tests for
this api already?  Why not use that here instead?

Also, this is userspace testing, not kunit testing, right, is that
intentional?  You are documenting internal apis and then writing
userspace tests for those apis, which feels a bit odd.

Also /dev/mem should not be used on "modern" systems, so how was this
tested?

> +// SPDX-License-Identifier: GPL-2.0+

Are you _sure_ you want GPLv2+?  I have to ask, sorry.

> +/*
> + * 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;

That's a funny define that is never used elsewhere.  I'm guessing this
was cut/pasted from some other userspace code somewhere?

> +
> +void deb_printf(const char *fmt, ...)

Who is "deb"?  You have more letters, always use them :)

Also, why debugging for just this one set of tests?  Don't kselftests
already have debugging logic?  if not, why is this unique to require it?

And am I missing something, or does this new tool not tie into the
kselftest framework properly?  I see lots of printing to output, but not
in the proper test framework format, am I just missing that somewhere?

thanks,

greg k-h
Re: [RFC v2 PATCH 3/3] selftests/devmem: initial testset
Posted by Alessandro Carminati 3 months, 3 weeks ago
Hi Greg,

On Tue, Oct 21, 2025 at 9:41 AM Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Wed, Sep 10, 2025 at 07:00:00PM +0200, Gabriele Paoloni wrote:
> > 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.
>
> Cool, but:
>
> >
> > 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 +++++
>
> That's a lot of files for a "simple" test.  Doesn't LTP have tests for
> this api already?  Why not use that here instead?
Indeed, at first glance the test may look simple.
However, despite the high-level concept being straightforward, there
are several corner cases and peculiar aspects that needed to be
covered, that’s why the test grew larger than expected.
For this RFC, my intent was mainly to share the full picture of what I
had in mind.
I fully agree that it can be refined and streamlined in the next iterations.
When I started working on this, I wasn’t aware of any existing LTP
tests for this interface.
After your comment, I double-checked, but I couldn’t find any relevant coverage.
As far as I can tell, neither LTP nor the upstream kernel selftests
(kselftest or KUnit) currently include tests specifically targeting
/dev/mem.

>
> Also, this is userspace testing, not kunit testing, right, is that
> intentional?  You are documenting internal apis and then writing
> userspace tests for those apis, which feels a bit odd.
Yes, that’s intentional.
The main reason is that all the interactions happen from userspace,
and the internal helper functions aren’t meant to be consumed by other
kernel components.
So I chose a kselftest-style approach.
That said, this is still an RFC, I’m happy to adapt it based on
community feedback and direction.

>
> Also /dev/mem should not be used on "modern" systems, so how was this
> tested?
That’s indeed one of the nuances I mentioned earlier.
Access to /dev/mem is often restricted for security reasons, so the
test is designed to detect and adapt to the environment, only
performing operations that are actually allowed.
>
> > +// SPDX-License-Identifier: GPL-2.0+
>
> Are you _sure_ you want GPLv2+?  I have to ask, sorry.
Yes, I used GPLv2+ deliberately, but I’m open to changing it if the
consensus is that GPLv2-only is more appropriate here.

>
> > +/*
> > + * 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;
>
> That's a funny define that is never used elsewhere.  I'm guessing this
> was cut/pasted from some other userspace code somewhere?
It wasn’t copied, but I agree it looks unnecessary in its current form.
I can remove or rework it in the next version.
>
> > +
> > +void deb_printf(const char *fmt, ...)
>
> Who is "deb"?  You have more letters, always use them :)
>
> Also, why debugging for just this one set of tests?  Don't kselftests
> already have debugging logic?  if not, why is this unique to require it?
>
> And am I missing something, or does this new tool not tie into the
> kselftest framework properly?  I see lots of printing to output, but not
> in the proper test framework format, am I just missing that somewhere?
You’re right, this was a small custom debug I left in place, mainly
because I needed a way to include additional information that isn’t
normally part of the standard selftest output, such as references to
specific requirements in the code.
My intent was to make it easier to correlate test results with those
requirements.
That said, I fully agree that the implementation isn’t aligned with
the kselftest framework style, and I’ll look into integrating this
need in a more standardized and consistent way in the next iteration.

>
> thanks,
>
> greg k-h
>
Thanks a lot for the detailed feedback, it’s very helpful.
I’ll revise the test accordingly for the next round.

Best regards,
Alessandro


--
---
172