diff --git a/tools/ringbuffer/Makefile b/tools/ringbuffer/Makefile
new file mode 100644
index 000000000000..2fb27a19b43e
--- /dev/null
+++ b/tools/ringbuffer/Makefile
@@ -0,0 +1,3 @@
+CFLAGS=-g -O2 -Wall -Werror -I../../include
+
+all: ringbuffer-test
diff --git a/tools/ringbuffer/ringbuffer-test.c b/tools/ringbuffer/ringbuffer-test.c
new file mode 100644
index 000000000000..0fba99e40858
--- /dev/null
+++ b/tools/ringbuffer/ringbuffer-test.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define READ	0
+#define WRITE	1
+
+#define min(a, b) (a < b ? a : b)
+
+#define __EXPORTED_HEADERS__
+#include <uapi/linux/ringbuffer_sys.h>
+
+#define BUF_NR		4
+
+typedef uint32_t u32;
+typedef unsigned long ulong;
+
+static inline struct ringbuffer_ptrs *ringbuffer(int fd, int rw, u32 size)
+{
+	ulong addr = 0;
+	int ret = syscall(463, fd, rw, size, &addr);
+	if (ret < 0)
+		errno = -ret;
+	return (void *) addr;
+}
+
+static inline int ringbuffer_wait(int fd, int rw)
+{
+	return syscall(464, fd, rw);
+}
+
+static inline int ringbuffer_wakeup(int fd, int rw)
+{
+	return syscall(465, fd, rw);
+}
+
+static ssize_t ringbuffer_read(int fd, struct ringbuffer_ptrs *rb,
+			       void *buf, size_t len)
+{
+	void *rb_data = (void *) rb + rb->data_offset;
+
+	u32 head, orig_tail = rb->tail, tail = orig_tail;
+
+	while ((head = __atomic_load_n(&rb->head, __ATOMIC_ACQUIRE)) == tail)
+		ringbuffer_wait(fd, READ);
+
+	while (len && head != tail) {
+		u32 tail_masked = tail & rb->mask;
+		unsigned b = min(len,
+			     min(head - tail,
+				 rb->size - tail_masked));
+
+		memcpy(buf, rb_data + tail_masked, b);
+		buf += b;
+		len -= b;
+		tail += b;
+	}
+
+	__atomic_store_n(&rb->tail, tail, __ATOMIC_RELEASE);
+
+	__atomic_thread_fence(__ATOMIC_SEQ_CST);
+
+	if (rb->head - orig_tail >= rb->size)
+		ringbuffer_wakeup(fd, READ);
+
+	return tail - orig_tail;
+}
+
+static ssize_t ringbuffer_write(int fd, struct ringbuffer_ptrs *rb,
+				void *buf, size_t len)
+{
+	void *rb_data = (void *) rb + rb->data_offset;
+
+	u32 orig_head = rb->head, head = orig_head, tail;
+
+	while (head - (tail = __atomic_load_n(&rb->tail, __ATOMIC_ACQUIRE)) >= rb->size)
+		ringbuffer_wait(fd, WRITE);
+
+	while (len && head - tail < rb->size) {
+		u32 head_masked = head & rb->mask;
+		unsigned b = min(len,
+			     min(tail - head + rb->size,
+				 rb->size - head_masked));
+
+		memcpy(rb_data + head_masked, buf, b);
+		buf += b;
+		len -= b;
+		head += b;
+	}
+
+	__atomic_store_n(&rb->head, head, __ATOMIC_RELEASE);
+
+	__atomic_thread_fence(__ATOMIC_SEQ_CST);
+
+	if ((s32) (rb->tail - orig_head) >= 0)
+		ringbuffer_wakeup(fd, WRITE);
+
+	return head - orig_head;
+}
+
+static void usage(void)
+{
+	puts("ringbuffer-test - test ringbuffer syscall\n"
+	     "Usage: ringbuffer-test [OPTION]...\n"
+	     "\n"
+	     "Options:\n"
+	     "      --type=(io|ringbuffer)\n"
+	     "      --rw=(read|write)\n"
+	     "  -h, --help                Display this help and exit\n");
+}
+
+static inline ssize_t rb_test_read(int fd, struct ringbuffer_ptrs *rb,
+				   void *buf, size_t len)
+{
+	return rb
+		? ringbuffer_read(fd, rb, buf, len)
+		: read(fd, buf, len);
+}
+
+static inline ssize_t rb_test_write(int fd, struct ringbuffer_ptrs *rb,
+				    void *buf, size_t len)
+{
+	return rb
+		? ringbuffer_write(fd, rb, buf, len)
+		: write(fd, buf, len);
+}
+
+int main(int argc, char *argv[])
+{
+	const struct option longopts[] = {
+		{ "type",		required_argument,	NULL, 't' },
+		{ "rw",			required_argument,	NULL, 'r' },
+		{ "help",		no_argument,		NULL, 'h' },
+		{ NULL }
+	};
+	int use_ringbuffer = false, rw = false;
+	int opt;
+
+	while ((opt = getopt_long(argc, argv, "h", longopts, NULL)) != -1) {
+		switch (opt) {
+		case 't':
+			if (!strcmp(optarg, "io"))
+				use_ringbuffer = false;
+			else if (!strcmp(optarg, "ringbuffer") ||
+				 !strcmp(optarg, "rb"))
+				use_ringbuffer = true;
+			else {
+				fprintf(stderr, "Invalid type %s\n", optarg);
+				exit(EXIT_FAILURE);
+			}
+			break;
+		case 'r':
+			if (!strcmp(optarg, "read"))
+				rw = false;
+			else if (!strcmp(optarg, "write"))
+				rw = true;
+			else {
+				fprintf(stderr, "Invalid rw %s\n", optarg);
+				exit(EXIT_FAILURE);
+			}
+			break;
+		case '?':
+			fprintf(stderr, "Invalid option %c\n", opt);
+			usage();
+			exit(EXIT_FAILURE);
+		case 'h':
+			usage();
+			exit(EXIT_SUCCESS);
+		}
+	}
+
+	int fd = open("/dev/ringbuffer-test", O_RDWR);
+	if (fd < 0) {
+		fprintf(stderr, "Error opening /dev/ringbuffer-test: %m\n");
+		exit(EXIT_FAILURE);
+	}
+
+	struct ringbuffer_ptrs *rb = NULL;
+	if (use_ringbuffer) {
+		rb = ringbuffer(fd, rw, 4096);
+		if (!rb) {
+			fprintf(stderr, "Error from sys_ringbuffer: %m\n");
+			exit(EXIT_FAILURE);
+		}
+
+		fprintf(stderr, "got ringbuffer %p\n", rb);
+	}
+
+	printf("Starting test with ringbuffer=%u, rw=%u\n", use_ringbuffer, rw);
+	static const char * const rw_str[] = { "read", "wrote" };
+
+	struct timeval start;
+	gettimeofday(&start, NULL);
+	size_t nr_prints = 1;
+
+	u32 buf[BUF_NR];
+	u32 idx = 0;
+
+	while (true) {
+		struct timeval now;
+		gettimeofday(&now, NULL);
+
+		struct timeval next_print = start;
+		next_print.tv_sec += nr_prints;
+
+		if (timercmp(&now, &next_print, >)) {
+			printf("%s %u u32s, %lu mb/sec\n", rw_str[rw], idx,
+			       (idx * sizeof(u32) / (now.tv_sec - start.tv_sec)) / (1UL << 20));
+			nr_prints++;
+			if (nr_prints > 20)
+				break;
+		}
+
+		if (rw == READ) {
+			int r = rb_test_read(fd, rb, buf, sizeof(buf));
+			if (r <= 0) {
+				fprintf(stderr, "Read returned %i (%m)\n", r);
+				exit(EXIT_FAILURE);
+			}
+
+			unsigned nr = r / sizeof(u32);
+			for (unsigned i = 0; i < nr; i++) {
+				if (buf[i] != idx + i) {
+					fprintf(stderr, "Read returned wrong data at idx %u: got %u instead\n",
+						idx + i, buf[i]);
+					exit(EXIT_FAILURE);
+				}
+			}
+
+			idx += nr;
+		} else {
+			for (unsigned i = 0; i < BUF_NR; i++)
+				buf[i] = idx + i;
+
+			int r = rb_test_write(fd, rb, buf, sizeof(buf));
+			if (r <= 0) {
+				fprintf(stderr, "Write returned %i (%m)\n", r);
+				exit(EXIT_FAILURE);
+			}
+
+			unsigned nr = r / sizeof(u32);
+			idx += nr;
+		}
+	}
+
+	exit(EXIT_SUCCESS);
+}
