Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933832AbcJZTWe (ORCPT ); Wed, 26 Oct 2016 15:22:34 -0400 Received: from mail-wm0-f68.google.com ([74.125.82.68]:33999 "EHLO mail-wm0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933446AbcJZTWL (ORCPT ); Wed, 26 Oct 2016 15:22:11 -0400 From: David Herrmann To: linux-kernel@vger.kernel.org Cc: Andy Lutomirski , Jiri Kosina , Greg KH , Hannes Reinecke , Steven Rostedt , Arnd Bergmann , Tom Gundersen , David Herrmann , Josh Triplett , Linus Torvalds , Andrew Morton Subject: [RFC v1 14/14] bus1: basic user-space kselftests Date: Wed, 26 Oct 2016 21:18:10 +0200 Message-Id: <20161026191810.12275-15-dh.herrmann@gmail.com> X-Mailer: git-send-email 2.10.1 In-Reply-To: <20161026191810.12275-1-dh.herrmann@gmail.com> References: <20161026191810.12275-1-dh.herrmann@gmail.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 25434 Lines: 1036 From: Tom Gundersen This adds kselftests integration and provides some basic API tests for bus1. Signed-off-by: Tom Gundersen Signed-off-by: David Herrmann --- 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 +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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/") */ + 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