summaryrefslogtreecommitdiffstats
path: root/auto-virtserial.c
diff options
context:
space:
mode:
Diffstat (limited to 'auto-virtserial.c')
-rw-r--r--auto-virtserial.c731
1 files changed, 731 insertions, 0 deletions
diff --git a/auto-virtserial.c b/auto-virtserial.c
new file mode 100644
index 0000000..dd7fb00
--- /dev/null
+++ b/auto-virtserial.c
@@ -0,0 +1,731 @@
+/*
+ * auto-virtserial: Exercise various options for virtio-serial ports
+ *
+ * This program is the controlling program to be run on the host.
+ *
+ * Assumptions:
+ * - The first port, that'll get seen as /dev/vcon1, will be the port
+ * for control information
+ * - The second port (vcon2) is started with no extra params (cache_buffers=1)
+ * - The third port (vcon3) is started with cache_buffers=0
+ * - The fourth port (vcon4) is started with host and guest limits to 1MB.
+ *
+ * Most of these tests have been described in the test-virtserial.c file
+ *
+ * Some information on the virtio serial ports is available at
+ * http://www.linux-kvm.org/page/VMchannel_Requirements
+ * https://fedoraproject.org/wiki/Features/VirtioSerial
+ *
+ * Copyright (C) 2009, Red Hat, Inc.
+ *
+ * Author(s):
+ * Amit Shah <amit.shah@redhat.com>
+ *
+ * Licensed under the GNU General Public License v2. See the file COPYING
+ * for more details.
+ */
+
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include "virtserial.h"
+
+#define DEBUG 1
+
+#ifdef DEBUG
+#define debug(fmt, ...) \
+ do { \
+ printf(fmt, __VA_ARGS__); \
+ } while(0)
+#else
+#define debug(fmt, ...) do { } while (0)
+#endif
+
+/*
+ * We test virtio-serial with 4 ports running at the same time.
+ * Port #0, as per tradition, is reserved for virtio-console
+ */
+#define NR_PORTS 5
+
+#define BUF_LENGTH 4080
+#define UNIX_PATH_MAX 108
+
+static int socks[NR_PORTS];
+static unsigned int nr_passed, nr_failed;
+
+static void handle_guest_error(struct guest_packet *gpkt)
+{
+ char *buf;
+
+ buf = malloc(gpkt->value);
+ if (!buf)
+ error(ENOMEM, ENOMEM, "Guest err");
+ read(socks[1], buf, gpkt->value);
+ fprintf(stderr, "guest error: %s\n", buf);
+ free(buf);
+}
+
+static int host_connect_chardev(const char *path, int nr)
+{
+ struct sockaddr_un sock;
+ int ret;
+
+ socks[nr] = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socks[nr] == -1)
+ error(errno, errno, "socket %d", nr);
+
+ sock.sun_family = AF_UNIX;
+ memcpy(&sock.sun_path, path, sizeof(sock.sun_path));
+ ret = connect(socks[nr], (struct sockaddr *)&sock, sizeof(sock));
+ if (ret == -1)
+ error(errno, errno, "connect: %s", path);
+ return 0;
+}
+
+static int host_close_chardev(int nr)
+{
+ return close(socks[nr]);
+}
+
+static int get_guest_response(struct guest_packet *gpkt)
+{
+ return read(socks[1], gpkt, sizeof(*gpkt));
+}
+
+static int guest_cmd_only(struct guest_packet *gpkt)
+{
+ return write(socks[1], gpkt, sizeof(*gpkt));
+}
+
+static int guest_cmd(struct guest_packet *gpkt)
+{
+ write(socks[1], gpkt, sizeof(*gpkt));
+ get_guest_response(gpkt);
+ if (gpkt->key == KEY_RESULT)
+ return gpkt->value;
+ return -1;
+}
+
+static int guest_open_port(int nr)
+{
+ struct guest_packet gpkt;
+
+ gpkt.key = KEY_OPEN;
+ gpkt.value = nr;
+ return guest_cmd(&gpkt);
+}
+
+static int guest_close_port(int nr)
+{
+ struct guest_packet gpkt;
+
+ gpkt.key = KEY_CLOSE;
+ gpkt.value = nr;
+ return guest_cmd(&gpkt);
+}
+
+static int guest_set_length(int nr, int len)
+{
+ struct guest_packet gpkt;
+
+ gpkt.key = KEY_LENGTH;
+ gpkt.value = len;
+ return guest_cmd(&gpkt);
+}
+
+static int guest_read(int nr, int len)
+{
+ struct guest_packet gpkt;
+ int ret;
+
+ ret = guest_set_length(nr, len);
+ if (ret)
+ return ret;
+
+ gpkt.key = KEY_READ;
+ gpkt.value = nr;
+
+ return guest_cmd(&gpkt);
+}
+
+static int guest_write(int nr, int len)
+{
+ struct guest_packet gpkt;
+ int ret;
+
+ ret = guest_set_length(nr, len);
+ if (ret)
+ return ret;
+
+ gpkt.key = KEY_WRITE;
+ gpkt.value = nr;
+
+ return guest_cmd(&gpkt);
+}
+
+static int guest_poll(int nr, int timeout)
+{
+ struct guest_packet gpkt;
+
+ gpkt.key = KEY_POLL;
+ gpkt.value = timeout;
+ return guest_cmd(&gpkt);
+}
+
+static int guest_set_port_nonblocking(int nr, int value)
+{
+ struct guest_packet gpkt;
+
+ gpkt.key = KEY_NONBLOCK;
+ gpkt.value = value;
+ return guest_cmd(&gpkt);
+}
+
+static void show_stats(void)
+{
+ fprintf(stderr, "-----\n");
+ fprintf(stderr, "Total tests passed: %u\n", nr_passed);
+ fprintf(stderr, "Total tests failed: %u\n", nr_failed);
+ fprintf(stderr, "-----\n");
+}
+
+static void pass(const char *msg, const char *subtype)
+{
+ fprintf(stderr, "%s(%s):\t\tPASS\n", msg, subtype);
+ nr_passed++;
+}
+
+static void fail(const char *msg, const char *subtype)
+{
+ fprintf(stderr, "%s(%s):\t\tFAIL\n", msg, subtype);
+ nr_failed++;
+}
+
+static int test_open(int nr)
+{
+ int ret;
+
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: return: %d\n", __func__, ret);
+ } else {
+ pass(__func__, "open");
+ }
+ return ret;
+}
+
+static int test_close(int nr)
+{
+ int ret;
+
+ ret = guest_close_port(nr);
+ if (ret) {
+ fail(__func__, "close");
+ debug("%s: return: %d\n", __func__, ret);
+ } else {
+ pass(__func__, "close");
+ }
+ return ret;
+}
+
+/* Reads should return 0 when host chardev isn't connected */
+static int test_read_without_host(int nr)
+{
+ int ret;
+
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: open: %d\n", __func__, ret);
+ return ret;
+ }
+ ret = guest_read(nr, 0);
+ if (ret)
+ fail(__func__, "read");
+ else
+ pass(__func__, "read");
+ guest_close_port(nr);
+ return ret;
+}
+
+static int test_blocking_read(const char *path, int nr)
+{
+ struct guest_packet gpkt;
+ struct pollfd pollfd[1];
+ int ret;
+
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: open: %d\n", __func__, ret);
+ return ret;
+ }
+ host_connect_chardev(path, nr);
+
+ guest_set_length(nr, sizeof(gpkt));
+ /* Reads should block now that the host is connected */
+ gpkt.key = KEY_READ;
+ gpkt.value = nr;
+ guest_cmd_only(&gpkt);
+
+ pollfd[0].fd = socks[1];
+ pollfd[0].events = POLLIN;
+ /* See if we get something in 5s -- we shouldn't. */
+ ret = poll(pollfd, 1, 5000);
+ if (ret == -1)
+ error(errno, errno, "%s: poll", __func__);
+ if (ret) {
+ fail(__func__, "poll");
+ debug("%s: poll: %d\n", __func__, ret);
+ ret = -1;
+ goto out;
+ }
+ /* Write out anything -- doesn't matter what it is */
+ write(socks[nr], &gpkt, sizeof(gpkt));
+ ret = poll(pollfd, 1, 5000);
+ if (ret != 1) {
+ fail(__func__, "poll");
+ debug("%s: no response from guest\n", __func__);
+ ret = -1;
+ goto out;
+ }
+ get_guest_response(&gpkt);
+ if (gpkt.key != KEY_RESULT)
+ error(EINVAL, EINVAL, "%s: guest response\n", __func__);
+ if (gpkt.value == sizeof(gpkt)) {
+ pass(__func__, "read");
+ ret = 0;
+ } else {
+ fail(__func__, "read");
+ debug("%s: expected return %zu, got %d\n",
+ __func__, sizeof(gpkt), gpkt.value);
+ ret = -EINVAL;
+ }
+out:
+ guest_close_port(nr);
+ host_close_chardev(nr);
+ return ret;
+}
+
+static int test_nonblocking_read(const char *path, int nr)
+{
+ struct guest_packet gpkt;
+ struct pollfd pollfd[1];
+ int ret;
+
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: open: %d\n", __func__, ret);
+ return ret;
+ }
+ host_connect_chardev(path, nr);
+
+ ret = guest_set_port_nonblocking(nr, true);
+ if (ret) {
+ fail(__func__, "blocking");
+ debug("%s: blocking: %d\n", __func__, -ret);
+ goto out;
+ }
+ guest_set_length(nr, sizeof(gpkt));
+ /* Reads should block now that the host is connected */
+ gpkt.key = KEY_READ;
+ gpkt.value = nr;
+ guest_cmd_only(&gpkt);
+
+ pollfd[0].fd = socks[1];
+ pollfd[0].events = POLLIN;
+ /* See if we get something in 5s -- we should. */
+ ret = poll(pollfd, 1, 5000);
+ if (ret == -1)
+ error(errno, errno, "%s: poll", __func__);
+ if (!ret) {
+ fail(__func__, "poll");
+ debug("%s: poll: %d\n", __func__, ret);
+ ret = -1;
+ goto out;
+ }
+ get_guest_response(&gpkt);
+ if (gpkt.value != -EAGAIN) {
+ fail(__func__, "guest response");
+ debug("%s: Expected %d got %d\n",
+ __func__, -EAGAIN, gpkt.value);
+ }
+ /* Write out anything -- doesn't matter what it is */
+ write(socks[nr], &gpkt, sizeof(gpkt));
+
+ ret = guest_read(nr, sizeof(gpkt));
+ if (ret == sizeof(gpkt)) {
+ pass(__func__, "read");
+ ret = 0;
+ } else {
+ fail(__func__, "read");
+ debug("%s: expected return %zu, got %d\n",
+ __func__, sizeof(gpkt), gpkt.value);
+ ret = -EINVAL;
+ }
+out:
+ guest_close_port(nr);
+ host_close_chardev(nr);
+ return ret;
+}
+
+static int test_poll(const char *path, int nr)
+{
+ int ret;
+
+ guest_open_port(nr);
+ ret = guest_poll(nr, 0);
+ if (ret < 0) {
+ fail(__func__, "open");
+ debug("%s: poll error: %d\n", __func__, ret);
+ goto out;
+ }
+ if (ret != POLLHUP) {
+ fail(__func__, "POLLHUP");
+ debug("%s: poll ret: 0x%x, expected 0x%x\n",
+ __func__, ret, POLLHUP);
+ goto out;
+ }
+ pass(__func__, "POLLHUP");
+
+ host_connect_chardev(path, nr);
+ ret = guest_poll(nr, 0);
+ if (ret < 0) {
+ fail(__func__, "poll");
+ debug("%s: poll error: %d\n", __func__, ret);
+ goto out_close;
+ } else if (ret == POLLOUT) {
+ pass(__func__, "POLLOUT");
+ } else {
+ fail(__func__, "POLLOUT");
+ debug("%s: poll ret: 0x%x, expected 0x%x\n",
+ __func__, ret, POLLOUT);
+ goto out_close;
+ }
+ write(socks[nr], &ret, sizeof(ret));
+ ret = guest_poll(nr, 0);
+ if (ret < 0) {
+ fail(__func__, "POLL");
+ debug("%s: poll error: %d\n", __func__, ret);
+ goto out_flush;
+ } else if (ret == (POLLIN|POLLOUT)) {
+ pass(__func__, "POLLIN");
+ } else {
+ fail(__func__, "POLLIN");
+ debug("%s: poll ret: 0x%d, expected 0x%d\n",
+ __func__, ret, POLLIN|POLLOUT);
+ goto out_flush;
+ }
+ ret = 0;
+out_flush:
+ guest_read(nr, sizeof(ret));
+out_close:
+ host_close_chardev(nr);
+out:
+ guest_close_port(nr);
+ return ret;
+}
+
+/*
+ * Tests if writes to guest get throttled after sending 1M data
+ * The invert parameter specifies the test should pass for ports
+ * that don't have throttling enabled
+ */
+static int test_guest_throttle(const char *path, int nr, bool enabled)
+{
+ char *buf;
+ struct pollfd pollfd[1];
+ size_t size, copied;
+ int ret;
+
+ /* Open guest */
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: open: %d\n", __func__, ret);
+ return ret;
+ }
+ host_connect_chardev(path, nr);
+
+ buf = malloc(BUF_LENGTH);
+ if (!buf)
+ error(ENOMEM, ENOMEM, "%s\n", __func__);
+
+ pollfd[0].fd = socks[nr];
+ pollfd[0].events = POLLOUT;
+ ret = poll(pollfd, 1, 0);
+ if (ret == -1)
+ error(errno, errno, "%s: poll\n", __func__);
+ if (!(pollfd[0].revents & POLLOUT))
+ error(ENOSPC, ENOSPC, "%s: can't write\n", __func__);
+
+ copied = 0;
+ do {
+ size = write(socks[nr], buf, BUF_LENGTH);
+ if (size == -1)
+ error(errno, errno, "%s: write", __func__);
+ copied += size;
+
+ ret = poll(pollfd, 1, 2000);
+ if (ret == -1)
+ error(errno, errno, "%s: poll\n", __func__);
+ } while ((pollfd[0].revents & POLLOUT) && (copied < 1048576));
+
+ if ((pollfd[0].revents & POLLOUT) && !enabled) {
+ pass(__func__, "disabled");
+ } else if (!(pollfd[0].revents & POLLOUT) && enabled) {
+ pass(__func__, "enabled");
+ } else {
+ fail(__func__, "throttle");
+ debug("%s: ret = %d, copied = %zu\n", __func__, ret, copied);
+ }
+ /* Cleanup - free all the buffers that were sent */
+ while (guest_poll(nr, 0) & POLLIN) {
+ guest_read(nr, BUF_LENGTH);
+ }
+ guest_close_port(nr);
+ host_close_chardev(nr);
+ free(buf);
+ return 0;
+}
+
+static int test_host_throttle(const char *path, int nr, bool enabled)
+{
+ char *buf;
+ struct pollfd pollfds[1];
+ size_t copied;
+ int ret;
+
+ ret = guest_open_port(nr);
+
+ copied = 0;
+ do {
+ ret = guest_write(nr, BUF_LENGTH);
+ if (ret > 0)
+ copied += ret;
+ } while ((copied < 1048576) && (ret > 0));
+
+ if (ret && !enabled) {
+ pass(__func__, "disabled");
+ } else if (!(ret == -ENOSPC) && enabled) {
+ pass(__func__, "enabled");
+ } else {
+ fail(__func__, "throttle");
+ debug("%s: ret = %d, copied = %zu\n", __func__, ret, copied);
+ }
+
+ host_connect_chardev(path, nr);
+ buf = malloc(BUF_LENGTH);
+ if (!buf)
+ error(ENOMEM, ENOMEM, "%s", __func__);
+ pollfds[0].fd = socks[nr];
+ pollfds[0].events = POLLIN;
+ while ((poll(pollfds, 1, 0) == 1) && (pollfds[0].revents & POLLIN))
+ read(socks[nr], buf, BUF_LENGTH);
+ free(buf);
+
+ host_close_chardev(nr);
+ guest_close_port(nr);
+ return ret;
+}
+
+static int test_guest_caching(const char *path, int nr, bool enabled)
+{
+ char *buf;
+ int ret;
+
+ ret = guest_open_port(nr);
+ if (ret) {
+ fail(__func__, "open");
+ debug("%s: open: %d\n", __func__, ret);
+ return ret;
+ }
+ host_connect_chardev(path, nr);
+
+ buf = malloc(BUF_LENGTH);
+ if (!buf)
+ error(ENOMEM, ENOMEM, "%s\n", __func__);
+
+ ret = write(socks[nr], buf, BUF_LENGTH);
+ if (ret == -1)
+ error(errno, errno, "%s: write", __func__);
+
+ ret = guest_close_port(nr);
+ host_close_chardev(nr);
+ free(buf);
+
+ ret = guest_open_port(nr);
+ ret = guest_read(nr, BUF_LENGTH);
+ if (enabled && (ret == BUF_LENGTH)) {
+ pass(__func__, "disabled");
+ } else if (!enabled && !ret) {
+ pass(__func__, "enabled");
+ } else {
+ fail(__func__, "caching");
+ debug("%s: ret = %d\n", __func__, ret);
+ }
+ guest_close_port(nr);
+ return 0;
+}
+
+static int test_host_caching(const char *path, int nr, bool enabled)
+{
+ int ret;
+ char *buf;
+ struct pollfd pollfds[1];
+
+ guest_open_port(nr);
+ ret = guest_write(nr, BUF_LENGTH);
+ if (ret != BUF_LENGTH) {
+ fail(__func__, "write");
+ debug("%s: guest_write return: %d\n", __func__, ret);
+ goto out;
+ }
+ host_connect_chardev(path, nr);
+
+ pollfds[0].fd = socks[nr];
+ pollfds[0].events = POLLIN;
+ ret = poll(pollfds, 1, 2000);
+ if (ret < 0) {
+ fail(__func__, "poll");
+ debug("%s: poll returned %d, revents %d\n",
+ __func__, ret, pollfds[0].revents);
+ goto out;
+ }
+ if (!enabled && ret > 0) {
+ fail(__func__, "disabled");
+ debug("%s: ret = %d, revents = %d\n",
+ __func__, ret, pollfds[0].revents);
+ goto out;
+ }
+ if (enabled && ret != 1) {
+ fail(__func__, "enabled");
+ debug("%s: ret = %d, revents = %d\n",
+ __func__, ret, pollfds[0].revents);
+ goto out;
+ }
+ if (!enabled) {
+ pass(__func__, "enabled");
+ goto out;
+ }
+ buf = malloc(BUF_LENGTH);
+ ret = read(socks[nr], buf, BUF_LENGTH);
+ free(buf);
+ if (ret == BUF_LENGTH) {
+ pass(__func__, "enabled");
+ } else {
+ fail(__func__, "caching");
+ if (ret == -1)
+ ret = -errno;
+ debug("%s: read %d\n", __func__, ret);
+ }
+out:
+ host_close_chardev(nr);
+ guest_close_port(nr);
+ return ret;
+}
+
+static int start_tests(const char *char1, const char *char2,
+ const char *char3, const char *char4)
+{
+ test_open(2);
+ test_close(2);
+ test_read_without_host(2);
+
+ test_blocking_read(char2, 2);
+ test_nonblocking_read(char2, 2);
+
+ test_poll(char2, 2);
+
+ /* Throttling is not enabled on this port */
+ test_guest_throttle(char2, 2, false);
+ /* Throttling is enabled on this port */
+ test_guest_throttle(char4, 4, true);
+
+ /* Throttling is not enabled on this port */
+ test_host_throttle(char2, 2, false);
+ /* Throttling is enabled on this port */
+ test_host_throttle(char4, 4, true);
+
+ /* Caching is enabled on this port */
+ test_guest_caching(char2, 2, true);
+ /* Caching is not enabled on this port */
+ test_guest_caching(char3, 3, false);
+
+ /* Caching is enabled on this port */
+ test_host_caching(char2, 2, true);
+ /* Caching is not enabled on this port */
+ test_host_caching(char3, 3, false);
+
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ struct guest_packet gpkt;
+ struct pollfd pollfd[1];
+ int i, ret;
+
+ if (argc < NR_PORTS) {
+ errno = EINVAL;
+ error(errno, errno, "Usage: %s <unix-1> <unix-2> <unix-3>\n",
+ argv[0]);
+ }
+ /* Check if host char drvs are ok */
+ for (i = 1; i < NR_PORTS; i++) {
+ ret = access(argv[i], R_OK|W_OK);
+ if (ret)
+ error(errno, errno, "access %s", argv[i]);
+ if (strlen(argv[i]) > UNIX_PATH_MAX)
+ error(E2BIG, E2BIG, "%s", argv[i]);
+ }
+ host_connect_chardev(argv[1], 1);
+ /*
+ * Send a message to the guest indicating we're ready. If the
+ * guest isn't ready yet, it'll connect and let us know.
+ */
+ gpkt.key = KEY_STATUS_OK;
+ gpkt.value = 1;
+ ret = write(socks[1], &gpkt, sizeof(gpkt));
+ if (ret == -1)
+ error(errno, errno, "write %s", argv[1]);
+ /* Now wait till we receive guest's response */
+ pollfd[0].fd = socks[1];
+ pollfd[0].events = POLLIN;
+ while (1) {
+ ret = poll(pollfd, 1, -1);
+ if (ret == -1)
+ error(errno, errno, "poll %s", argv[1]);
+ debug("poll revents = %u\n", pollfd[0].revents);
+ if (pollfd[0].revents & POLLIN) {
+ ret = read(socks[1], &gpkt, sizeof(gpkt));
+ if (ret < sizeof(gpkt))
+ error(EINVAL, EINVAL, "Read error");
+ if (gpkt.key == KEY_STATUS_OK && gpkt.value) {
+ debug("Guest is up %d\n", gpkt.key);
+ break;
+ }
+ if (gpkt.key == KEY_GUEST_ERR) {
+ handle_guest_error(&gpkt);
+ error(EINVAL, EINVAL, "Guest error");
+ }
+ }
+ }
+ /* Now we're all set to start our tests. */
+ start_tests(argv[1], argv[2], argv[3], argv[4]);
+ show_stats();
+ host_close_chardev(1);
+
+ return nr_failed;
+}