diff options
Diffstat (limited to 'auto-virtserial.c')
-rw-r--r-- | auto-virtserial.c | 731 |
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; +} |