[RFC v1 14/14] bus1: basic user-space kselftests

From: David Herrmann
Date: Wed Oct 26 2016 - 15:22:37 EST


From: Tom Gundersen <teg@xxxxxxx>

This adds kselftests integration and provides some basic API tests for
bus1.

Signed-off-by: Tom Gundersen <teg@xxxxxxx>
Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
---
tools/testing/selftests/bus1/.gitignore | 2 +
tools/testing/selftests/bus1/Makefile | 19 ++
tools/testing/selftests/bus1/bus1-ioctl.h | 111 +++++++
tools/testing/selftests/bus1/test-api.c | 532 ++++++++++++++++++++++++++++++
tools/testing/selftests/bus1/test-io.c | 198 +++++++++++
tools/testing/selftests/bus1/test.h | 114 +++++++
6 files changed, 976 insertions(+)
create mode 100644 tools/testing/selftests/bus1/.gitignore
create mode 100644 tools/testing/selftests/bus1/Makefile
create mode 100644 tools/testing/selftests/bus1/bus1-ioctl.h
create mode 100644 tools/testing/selftests/bus1/test-api.c
create mode 100644 tools/testing/selftests/bus1/test-io.c
create mode 100644 tools/testing/selftests/bus1/test.h

diff --git a/tools/testing/selftests/bus1/.gitignore b/tools/testing/selftests/bus1/.gitignore
new file mode 100644
index 0000000..76ecb9c
--- /dev/null
+++ b/tools/testing/selftests/bus1/.gitignore
@@ -0,0 +1,2 @@
+test-api
+test-io
diff --git a/tools/testing/selftests/bus1/Makefile b/tools/testing/selftests/bus1/Makefile
new file mode 100644
index 0000000..cbcf689
--- /dev/null
+++ b/tools/testing/selftests/bus1/Makefile
@@ -0,0 +1,19 @@
+# Makefile for bus1 selftests
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS += -D_FILE_OFFSET_BITS=64 -Wall -g -O2
+CFLAGS += -I../../../../include/uapi/
+CFLAGS += -I../../../../include/
+CFLAGS += -I../../../../usr/include/
+
+TEST_PROGS := test-api test-io
+
+all: $(TEST_PROGS)
+
+%: %.c bus1-ioctl.h test.h ../../../../usr/include/linux/bus1.h
+ $(CC) $(CFLAGS) $< -o $@
+
+include ../lib.mk
+
+clean:
+ $(RM) $(TEST_PROGS)
diff --git a/tools/testing/selftests/bus1/bus1-ioctl.h b/tools/testing/selftests/bus1/bus1-ioctl.h
new file mode 100644
index 0000000..552bd5d
--- /dev/null
+++ b/tools/testing/selftests/bus1/bus1-ioctl.h
@@ -0,0 +1,111 @@
+#pragma once
+
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <linux/bus1.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static inline int
+bus1_ioctl(int fd, unsigned int cmd, void *arg)
+{
+ return (ioctl(fd, cmd, arg) >= 0) ? 0: -errno;
+}
+
+static inline int
+bus1_ioctl_peer_disconnect(int fd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_PEER_DISCONNECT) == sizeof(uint64_t),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_PEER_DISCONNECT, NULL);
+}
+
+static inline int
+bus1_ioctl_peer_query(int fd, struct bus1_cmd_peer_reset *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_PEER_QUERY) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_PEER_QUERY, cmd);
+}
+
+static inline int
+bus1_ioctl_peer_reset(int fd, struct bus1_cmd_peer_reset *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_PEER_RESET) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_PEER_RESET, cmd);
+}
+
+static inline int
+bus1_ioctl_handle_release(int fd, uint64_t *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_HANDLE_RELEASE) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_HANDLE_RELEASE, cmd);
+}
+
+static inline int
+bus1_ioctl_handle_transfer(int fd, struct bus1_cmd_handle_transfer *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_HANDLE_TRANSFER) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_HANDLE_TRANSFER, cmd);
+}
+
+static inline int
+bus1_ioctl_nodes_destroy(int fd, struct bus1_cmd_nodes_destroy *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_NODES_DESTROY) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_NODES_DESTROY, cmd);
+}
+
+static inline int
+bus1_ioctl_slice_release(int fd, uint64_t *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_SLICE_RELEASE) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_SLICE_RELEASE, cmd);
+}
+
+static inline int
+bus1_ioctl_send(int fd, struct bus1_cmd_send *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_SEND) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_SEND, cmd);
+}
+
+static inline int
+bus1_ioctl_recv(int fd, struct bus1_cmd_recv *cmd)
+{
+ static_assert(_IOC_SIZE(BUS1_CMD_RECV) == sizeof(*cmd),
+ "ioctl is called with invalid argument size");
+
+ return bus1_ioctl(fd, BUS1_CMD_RECV, cmd);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tools/testing/selftests/bus1/test-api.c b/tools/testing/selftests/bus1/test-api.c
new file mode 100644
index 0000000..a289197
--- /dev/null
+++ b/tools/testing/selftests/bus1/test-api.c
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include "test.h"
+
+/* make sure /dev/busX exists, is a cdev and accessible */
+static void test_api_cdev(void)
+{
+ const uint8_t *map;
+ struct stat st;
+ size_t n_map;
+ int r, fd;
+
+ r = access(test_path, F_OK);
+ assert(r >= 0);
+
+ r = stat(test_path, &st);
+ assert(r >= 0);
+ assert((st.st_mode & S_IFMT) == S_IFCHR);
+
+ r = open(test_path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
+ assert(r >= 0);
+ close(r);
+
+ fd = test_open(&map, &n_map);
+ test_close(fd, map, n_map);
+}
+
+/* make sure basic connect works */
+static void test_api_connect(void)
+{
+ struct bus1_cmd_peer_reset cmd_reset = {
+ .flags = 0,
+ .peer_flags = -1,
+ .max_slices = -1,
+ .max_handles = -1,
+ .max_inflight_bytes = -1,
+ .max_inflight_fds = -1,
+ };
+ const uint8_t *map1;
+ size_t n_map1;
+ int r, fd1;
+
+ /* create @fd1 */
+
+ fd1 = test_open(&map1, &n_map1);
+
+ /* test empty RESET */
+
+ r = bus1_ioctl_peer_reset(fd1, &cmd_reset);
+ assert(r >= 0);
+
+ /* test DISCONNECT and verify ESHUTDOWN afterwards */
+
+ r = bus1_ioctl_peer_disconnect(fd1);
+ assert(r >= 0);
+
+ r = bus1_ioctl_peer_disconnect(fd1);
+ assert(r < 0);
+ assert(r == -ESHUTDOWN);
+
+ r = bus1_ioctl_peer_reset(fd1, &cmd_reset);
+ assert(r < 0);
+ assert(r == -ESHUTDOWN);
+
+ /* cleanup */
+
+ test_close(fd1, map1, n_map1);
+}
+
+/* make sure basic transfer works */
+static void test_api_transfer(void)
+{
+ struct bus1_cmd_handle_transfer cmd_transfer;
+ const uint8_t *map1, *map2;
+ size_t n_map1, n_map2;
+ int r, fd1, fd2;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+ fd2 = test_open(&map2, &n_map2);
+
+ /* import a handle from @fd1 into @fd2 */
+
+ cmd_transfer = (struct bus1_cmd_handle_transfer){
+ .flags = 0,
+ .src_handle = 0x100,
+ .dst_fd = fd2,
+ .dst_handle = BUS1_HANDLE_INVALID,
+ };
+ r = bus1_ioctl_handle_transfer(fd1, &cmd_transfer);
+ assert(r >= 0);
+ assert(cmd_transfer.dst_handle != BUS1_HANDLE_INVALID);
+ assert(cmd_transfer.dst_handle & BUS1_HANDLE_FLAG_MANAGED);
+ assert(cmd_transfer.dst_handle & BUS1_HANDLE_FLAG_REMOTE);
+
+ /* cleanup */
+
+ test_close(fd2, map2, n_map2);
+ test_close(fd1, map1, n_map1);
+}
+
+/* test release notification */
+static void test_api_notify_release(void)
+{
+ struct bus1_cmd_handle_transfer cmd_transfer;
+ struct bus1_cmd_recv cmd_recv;
+ const uint8_t *map1;
+ uint64_t id = 0x100;
+ size_t n_map1;
+ int r, fd1;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+
+ /* import a handle from @fd1 into @fd2 */
+
+ cmd_transfer = (struct bus1_cmd_handle_transfer){
+ .flags = 0,
+ .src_handle = id,
+ .dst_fd = -1,
+ .dst_handle = BUS1_HANDLE_INVALID,
+ };
+ r = bus1_ioctl_handle_transfer(fd1, &cmd_transfer);
+ assert(r >= 0);
+ assert(cmd_transfer.dst_handle == id);
+
+ /* no message can be queued */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* release handle to trigger release notification */
+
+ r = bus1_ioctl_handle_release(fd1, &id);
+ assert(r == 0);
+
+ /* dequeue release notification */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_NODE_RELEASE);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == id);
+
+ /* no more messages */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /*
+ * Trigger the same thing again.
+ */
+
+ cmd_transfer = (struct bus1_cmd_handle_transfer){
+ .flags = 0,
+ .src_handle = id,
+ .dst_fd = -1,
+ .dst_handle = BUS1_HANDLE_INVALID,
+ };
+ r = bus1_ioctl_handle_transfer(fd1, &cmd_transfer);
+ assert(r >= 0);
+ assert(cmd_transfer.dst_handle == id);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ r = bus1_ioctl_handle_release(fd1, &id);
+ assert(r == 0);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_NODE_RELEASE);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == id);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* cleanup */
+
+ test_close(fd1, map1, n_map1);
+}
+
+/* test destroy notification */
+static void test_api_notify_destroy(void)
+{
+ struct bus1_cmd_handle_transfer cmd_transfer;
+ struct bus1_cmd_nodes_destroy cmd_destroy;
+ struct bus1_cmd_recv cmd_recv;
+ uint64_t node = 0x100, handle;
+ const uint8_t *map1, *map2;
+ size_t n_map1, n_map2;
+ int r, fd1, fd2;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+ fd2 = test_open(&map2, &n_map2);
+
+ /* import a handle from @fd1 into @fd2 */
+
+ cmd_transfer = (struct bus1_cmd_handle_transfer){
+ .flags = 0,
+ .src_handle = node,
+ .dst_fd = fd2,
+ .dst_handle = BUS1_HANDLE_INVALID,
+ };
+ r = bus1_ioctl_handle_transfer(fd1, &cmd_transfer);
+ assert(r >= 0);
+ handle = cmd_transfer.dst_handle;
+
+ /* both queues must be empty */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map2,
+ };
+ r = bus1_ioctl_recv(fd2, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* destroy node and trigger destruction notification */
+
+ cmd_destroy = (struct bus1_cmd_nodes_destroy){
+ .flags = 0,
+ .ptr_nodes = (unsigned long)&node,
+ .n_nodes = 1,
+ };
+ r = bus1_ioctl_nodes_destroy(fd1, &cmd_destroy);
+ assert(r >= 0);
+
+ /* dequeue destruction notification */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_NODE_DESTROY);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == node);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd2, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_NODE_DESTROY);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == handle);
+
+ /* cleanup */
+
+ test_close(fd2, map2, n_map2);
+ test_close(fd1, map1, n_map1);
+}
+
+/* make sure basic unicasts works */
+static void test_api_unicast(void)
+{
+ struct bus1_cmd_send cmd_send;
+ struct bus1_cmd_recv cmd_recv;
+ const uint8_t *map1;
+ uint64_t id = 0x100;
+ size_t n_map1;
+ int r, fd1;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+
+ /* send empty message */
+
+ cmd_send = (struct bus1_cmd_send){
+ .flags = 0,
+ .ptr_destinations = (unsigned long)&id,
+ .ptr_errors = 0,
+ .n_destinations = 1,
+ .ptr_vecs = 0,
+ .n_vecs = 0,
+ .ptr_handles = 0,
+ .n_handles = 0,
+ .ptr_fds = 0,
+ .n_fds = 0,
+ };
+ r = bus1_ioctl_send(fd1, &cmd_send);
+ assert(r >= 0);
+
+ /* retrieve empty message */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_DATA);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == id);
+
+ /* queue must be empty now */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* cleanup */
+
+ test_close(fd1, map1, n_map1);
+}
+
+/* make sure basic multicasts works */
+static void test_api_multicast(void)
+{
+ struct bus1_cmd_send cmd_send;
+ struct bus1_cmd_recv cmd_recv;
+ uint64_t ids[] = { 0x100, 0x200 };
+ uint64_t data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
+ struct iovec vec = { data, sizeof(data) };
+ const uint8_t *map1;
+ size_t n_map1;
+ int r, fd1;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+
+ /* send multicast */
+
+ cmd_send = (struct bus1_cmd_send){
+ .flags = 0,
+ .ptr_destinations = (unsigned long)ids,
+ .ptr_errors = 0,
+ .n_destinations = sizeof(ids) / sizeof(*ids),
+ .ptr_vecs = (unsigned long)&vec,
+ .n_vecs = 1,
+ .ptr_handles = 0,
+ .n_handles = 0,
+ .ptr_fds = 0,
+ .n_fds = 0,
+ };
+ r = bus1_ioctl_send(fd1, &cmd_send);
+ assert(r >= 0);
+
+ /* retrieve messages */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_DATA);
+ assert(cmd_recv.msg.flags == BUS1_MSG_FLAG_CONTINUE);
+ assert(cmd_recv.msg.destination == ids[0] ||
+ cmd_recv.msg.destination == ids[1]);
+ assert(cmd_recv.msg.n_bytes == sizeof(data));
+ assert(!memcmp(map1 + cmd_recv.msg.offset, data, sizeof(data)));
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_DATA);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == ids[0] ||
+ cmd_recv.msg.destination == ids[1]);
+ assert(cmd_recv.msg.n_bytes == sizeof(data));
+ assert(!memcmp(map1 + cmd_recv.msg.offset, data, sizeof(data)));
+
+ /* queue must be empty now */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* cleanup */
+
+ test_close(fd1, map1, n_map1);
+}
+
+/* make sure basic payload-handles work */
+static void test_api_handle(void)
+{
+ struct bus1_cmd_send cmd_send;
+ struct bus1_cmd_recv cmd_recv;
+ uint64_t id = 0x100;
+ const uint8_t *map1;
+ size_t n_map1;
+ int r, fd1;
+
+ /* setup */
+
+ fd1 = test_open(&map1, &n_map1);
+
+ /* send message */
+
+ cmd_send = (struct bus1_cmd_send){
+ .flags = 0,
+ .ptr_destinations = (unsigned long)&id,
+ .ptr_errors = 0,
+ .n_destinations = 1,
+ .ptr_vecs = 0,
+ .n_vecs = 0,
+ .ptr_handles = (unsigned long)&id,
+ .n_handles = 1,
+ .ptr_fds = 0,
+ .n_fds = 0,
+ };
+ r = bus1_ioctl_send(fd1, &cmd_send);
+ assert(r >= 0);
+
+ /* retrieve messages */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_DATA);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == id);
+ assert(cmd_recv.msg.n_handles == 1);
+
+ /* queue must be empty now */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* releasing one reference must trigger a release notification */
+
+ r = bus1_ioctl_handle_release(fd1, &id);
+ assert(r >= 0);
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_NODE_RELEASE);
+ assert(cmd_recv.msg.flags == 0);
+ assert(cmd_recv.msg.destination == id);
+
+ /* queue must be empty again */
+
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = n_map1,
+ };
+ r = bus1_ioctl_recv(fd1, &cmd_recv);
+ assert(r == -EAGAIN);
+
+ /* cleanup */
+
+ test_close(fd1, map1, n_map1);
+}
+
+int main(int argc, char **argv)
+{
+ int r;
+
+ r = test_parse_argv(argc, argv);
+ if (r > 0) {
+ test_api_cdev();
+ test_api_connect();
+ test_api_transfer();
+ test_api_notify_release();
+ test_api_notify_destroy();
+ test_api_unicast();
+ test_api_multicast();
+ test_api_handle();
+ }
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/tools/testing/selftests/bus1/test-io.c b/tools/testing/selftests/bus1/test-io.c
new file mode 100644
index 0000000..6cb48e7
--- /dev/null
+++ b/tools/testing/selftests/bus1/test-io.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <time.h>
+#include "test.h"
+
+#define MAX_DESTINATIONS (256)
+
+static inline uint64_t nsec_from_clock(clockid_t clock)
+{
+ struct timespec ts;
+ int r;
+
+ r = clock_gettime(clock, &ts);
+ assert(r >= 0);
+ return ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
+}
+
+static void test_one_uds(int uds[2], void *payload, size_t n_bytes)
+{
+ int r;
+
+ /* send */
+ r = write(uds[0], payload, n_bytes);
+ assert(r == n_bytes);
+
+ /* receive */
+ r = recv(uds[1], payload, n_bytes, 0);
+ assert(r == n_bytes);
+}
+
+static uint64_t test_iterate_uds(unsigned int iterations, size_t n_bytes)
+{
+ int uds[2];
+ char payload[n_bytes];
+ unsigned int i;
+ uint64_t time_start, time_end;
+ int r;
+
+ /* create socket pair */
+ r = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, uds);
+ assert(r >= 0);
+
+ /* caches */
+ test_one_uds(uds, payload, n_bytes);
+
+ time_start = nsec_from_clock(CLOCK_THREAD_CPUTIME_ID);
+ for (i = 0; i < iterations; i++)
+ test_one_uds(uds, payload, n_bytes);
+ time_end = nsec_from_clock(CLOCK_THREAD_CPUTIME_ID);
+
+ /* cleanup */
+ close(uds[0]);
+ close(uds[1]);
+
+ return (time_end - time_start) / iterations;
+}
+
+static void test_one(int fd1,
+ int *fds,
+ uint64_t *handles,
+ unsigned int n_destinations,
+ char *payload,
+ size_t n_bytes)
+{
+ struct bus1_cmd_send cmd_send;
+ struct bus1_cmd_recv cmd_recv;
+ struct iovec vec = { payload, n_bytes };
+ size_t i;
+ int r;
+
+ cmd_send = (struct bus1_cmd_send){
+ .flags = 0,
+ .ptr_destinations = (unsigned long)handles,
+ .ptr_errors = 0,
+ .n_destinations = n_destinations,
+ .ptr_vecs = (unsigned long)&vec,
+ .n_vecs = 1,
+ .ptr_handles = 0,
+ .n_handles = 0,
+ .ptr_fds = 0,
+ .n_fds = 0,
+ };
+ r = bus1_ioctl_send(fd1, &cmd_send);
+ assert(r >= 0);
+
+ for (i = 0; i < n_destinations; ++i) {
+ cmd_recv = (struct bus1_cmd_recv){
+ .flags = 0,
+ .max_offset = -1,
+ };
+ r = bus1_ioctl_recv(fds[i], &cmd_recv);
+ assert(r >= 0);
+ assert(cmd_recv.msg.type == BUS1_MSG_DATA);
+ assert(cmd_recv.msg.n_bytes == n_bytes);
+
+ r = bus1_ioctl_slice_release(fds[i],
+ (uint64_t *)&cmd_recv.msg.offset);
+ assert(r >= 0);
+ }
+}
+
+static uint64_t test_iterate(unsigned int iterations,
+ unsigned int n_destinations,
+ size_t n_bytes)
+{
+ struct bus1_cmd_handle_transfer cmd_transfer;
+ const uint8_t *maps[MAX_DESTINATIONS + 1];
+ size_t n_maps[MAX_DESTINATIONS + 1];
+ uint64_t handles[MAX_DESTINATIONS + 1];
+ int r, fds[MAX_DESTINATIONS + 1];
+ uint64_t time_start, time_end;
+ char payload[n_bytes];
+ size_t i;
+
+ assert(n_destinations <= MAX_DESTINATIONS);
+
+ /* setup */
+ fds[0] = test_open(&maps[0], &n_maps[0]);
+
+ for (i = 1; i < sizeof(fds) / sizeof(*fds); ++i) {
+ fds[i] = test_open(&maps[i], &n_maps[i]);
+
+ cmd_transfer = (struct bus1_cmd_handle_transfer){
+ .flags = 0,
+ .src_handle = 0x100,
+ .dst_fd = fds[0],
+ .dst_handle = BUS1_HANDLE_INVALID,
+ };
+ r = bus1_ioctl_handle_transfer(fds[i], &cmd_transfer);
+ assert(r >= 0);
+ handles[i] = cmd_transfer.dst_handle;
+ }
+
+ /* caches */
+ test_one(fds[0], fds + 1, handles + 1, n_destinations, payload,
+ n_bytes);
+
+ time_start = nsec_from_clock(CLOCK_THREAD_CPUTIME_ID);
+ for (i = 0; i < iterations; i++)
+ test_one(fds[0], fds + 1, handles + 1, n_destinations, payload,
+ n_bytes);
+ time_end = nsec_from_clock(CLOCK_THREAD_CPUTIME_ID);
+
+ for (i = 0; i < sizeof(fds) / sizeof(*fds); ++i)
+ test_close(fds[i], maps[i], n_maps[i]);
+
+ return (time_end - time_start) / iterations;
+}
+
+static void test_io(void)
+{
+ unsigned long base;
+ unsigned int i;
+
+ fprintf(stderr, "UDS took %lu ns without payload\n",
+ test_iterate_uds(100000, 0));
+ fprintf(stderr, "UDS took %lu ns\n",
+ test_iterate_uds(100000, 1024));
+
+ base = test_iterate(1000000, 0, 1024);
+
+ fprintf(stderr, "it took %lu ns for no destinations\n", base);
+ fprintf(stderr,
+ "it took %lu ns + %lu ns for one destination without payload\n",
+ base, test_iterate(100000, 1, 0) - base);
+ fprintf(stderr, "it took %lu ns + %lu ns for one destination\n", base,
+ test_iterate(100000, 1, 1024) - base);
+
+ for (i = 1; i < 9; ++i) {
+ unsigned int dests = 1UL << i;
+
+ fprintf(stderr, "it took %lu ns + %lu ns per destination for %u destinations\n",
+ base, (test_iterate(100000 >> i, dests, 1024) - base) / dests, dests);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int r;
+
+ r = test_parse_argv(argc, argv);
+ if (r > 0) {
+ test_io();
+ }
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/tools/testing/selftests/bus1/test.h b/tools/testing/selftests/bus1/test.h
new file mode 100644
index 0000000..fee815e
--- /dev/null
+++ b/tools/testing/selftests/bus1/test.h
@@ -0,0 +1,114 @@
+#ifndef __TEST_H
+#define __TEST_H
+
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+/* include standard environment for all tests */
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/bus1.h>
+#include <linux/sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "bus1-ioctl.h"
+
+static char *test_path;
+static char *test_arg_module = "bus1";
+
+#define c_align_to(_val, _to) (((_val) + (_to) - 1) & ~((_to) - 1))
+
+static inline int test_parse_argv(int argc, char **argv)
+{
+ enum {
+ ARG_MODULE = 0x100,
+ };
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "module", required_argument, NULL, ARG_MODULE },
+ {}
+ };
+ char *t;
+ int c;
+
+ t = getenv("BUS1EXT");
+ if (t) {
+ test_arg_module = malloc(strlen(t) + 4);
+ assert(test_arg_module);
+ strcpy(test_arg_module, "bus");
+ strcpy(test_arg_module + 3, t);
+ }
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+ switch (c) {
+ case 'h':
+ fprintf(stderr,
+ "Usage: %s [OPTIONS...] ...\n\n"
+ "Run bus1 test.\n\n"
+ "\t-h, --help Print this help\n"
+ "\t --module=bus1 Module to use\n"
+ , program_invocation_short_name);
+
+ return 0;
+
+ case ARG_MODULE:
+ test_arg_module = optarg;
+ break;
+
+ case '?':
+ /* fallthrough */
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* store cdev-path for tests to access ("/dev/<module>") */
+ free(test_path);
+ test_path = malloc(strlen(test_arg_module) + 6);
+ assert(test_path);
+ strcpy(test_path, "/dev/");
+ strcpy(test_path + 5, test_arg_module);
+
+ return 1;
+}
+
+static inline int test_open(const uint8_t **mapp, size_t *n_mapp)
+{
+ const size_t size = 16UL * 1024UL * 1024UL;
+ int fd;
+
+ fd = open(test_path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
+ assert(fd >= 0);
+
+ *mapp = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ assert(*mapp != MAP_FAILED);
+
+ *n_mapp = size;
+ return fd;
+}
+
+static inline void test_close(int fd, const uint8_t *map, size_t n_map)
+{
+ munmap((void *)map, n_map);
+ close(fd);
+}
+
+#endif /* __TEST_H */
--
2.10.1