root/tools/virtio/ringtest/main.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2016 Red Hat, Inc.
 * Author: Michael S. Tsirkin <mst@redhat.com>
 *
 * Command line processing and common functions for ring benchmarking.
 */
#define _GNU_SOURCE
#include <getopt.h>
#include <pthread.h>
#include <assert.h>
#include <sched.h>
#include "main.h"
#include <sys/eventfd.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

int runcycles = 10000000;
int max_outstanding = INT_MAX;
int batch = 1;
int param = 0;

bool do_sleep = false;
bool do_relax = false;
bool do_exit = true;

unsigned ring_size = 256;

static int kickfd = -1;
static int callfd = -1;

void notify(int fd)
{
        unsigned long long v = 1;
        int r;

        vmexit();
        r = write(fd, &v, sizeof v);
        assert(r == sizeof v);
        vmentry();
}

void wait_for_notify(int fd)
{
        unsigned long long v = 1;
        int r;

        vmexit();
        r = read(fd, &v, sizeof v);
        assert(r == sizeof v);
        vmentry();
}

void kick(void)
{
        notify(kickfd);
}

void wait_for_kick(void)
{
        wait_for_notify(kickfd);
}

void call(void)
{
        notify(callfd);
}

void wait_for_call(void)
{
        wait_for_notify(callfd);
}

void set_affinity(const char *arg)
{
        cpu_set_t cpuset;
        int ret;
        pthread_t self;
        long int cpu;
        char *endptr;

        if (!arg)
                return;

        cpu = strtol(arg, &endptr, 0);
        assert(!*endptr);

        assert(cpu >= 0 && cpu < CPU_SETSIZE);

        self = pthread_self();
        CPU_ZERO(&cpuset);
        CPU_SET(cpu, &cpuset);

        ret = pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset);
        assert(!ret);
}

void poll_used(void)
{
        while (used_empty())
                busy_wait();
}

static void __attribute__((__flatten__)) run_guest(void)
{
        int completed_before;
        int completed = 0;
        int started = 0;
        int bufs = runcycles;
        int spurious = 0;
        int r;
        unsigned len;
        void *buf;
        int tokick = batch;

        for (;;) {
                if (do_sleep)
                        disable_call();
                completed_before = completed;
                do {
                        if (started < bufs &&
                            started - completed < max_outstanding) {
                                r = add_inbuf(0, "Buffer\n", "Hello, world!");
                                if (__builtin_expect(r == 0, true)) {
                                        ++started;
                                        if (!--tokick) {
                                                tokick = batch;
                                                if (do_sleep)
                                                        kick_available();
                                        }

                                }
                        } else
                                r = -1;

                        /* Flush out completed bufs if any */
                        if (get_buf(&len, &buf)) {
                                ++completed;
                                if (__builtin_expect(completed == bufs, false))
                                        return;
                                r = 0;
                        }
                } while (r == 0);
                if (completed == completed_before)
                        ++spurious;
                assert(completed <= bufs);
                assert(started <= bufs);
                if (do_sleep) {
                        if (used_empty() && enable_call())
                                wait_for_call();
                } else {
                        poll_used();
                }
        }
}

void poll_avail(void)
{
        while (avail_empty())
                busy_wait();
}

static void __attribute__((__flatten__)) run_host(void)
{
        int completed_before;
        int completed = 0;
        int spurious = 0;
        int bufs = runcycles;
        unsigned len;
        void *buf;

        for (;;) {
                if (do_sleep) {
                        if (avail_empty() && enable_kick())
                                wait_for_kick();
                } else {
                        poll_avail();
                }
                if (do_sleep)
                        disable_kick();
                completed_before = completed;
                while (__builtin_expect(use_buf(&len, &buf), true)) {
                        if (do_sleep)
                                call_used();
                        ++completed;
                        if (__builtin_expect(completed == bufs, false))
                                return;
                }
                if (completed == completed_before)
                        ++spurious;
                assert(completed <= bufs);
                if (completed == bufs)
                        break;
        }
}

void *start_guest(void *arg)
{
        set_affinity(arg);
        run_guest();
        pthread_exit(NULL);
}

void *start_host(void *arg)
{
        set_affinity(arg);
        run_host();
        pthread_exit(NULL);
}

static const char optstring[] = "";
static const struct option longopts[] = {
        {
                .name = "help",
                .has_arg = no_argument,
                .val = 'h',
        },
        {
                .name = "host-affinity",
                .has_arg = required_argument,
                .val = 'H',
        },
        {
                .name = "guest-affinity",
                .has_arg = required_argument,
                .val = 'G',
        },
        {
                .name = "ring-size",
                .has_arg = required_argument,
                .val = 'R',
        },
        {
                .name = "run-cycles",
                .has_arg = required_argument,
                .val = 'C',
        },
        {
                .name = "outstanding",
                .has_arg = required_argument,
                .val = 'o',
        },
        {
                .name = "batch",
                .has_arg = required_argument,
                .val = 'b',
        },
        {
                .name = "param",
                .has_arg = required_argument,
                .val = 'p',
        },
        {
                .name = "sleep",
                .has_arg = no_argument,
                .val = 's',
        },
        {
                .name = "relax",
                .has_arg = no_argument,
                .val = 'x',
        },
        {
                .name = "exit",
                .has_arg = no_argument,
                .val = 'e',
        },
        {
        }
};

static void help(void)
{
        fprintf(stderr, "Usage: <test> [--help]"
                " [--host-affinity H]"
                " [--guest-affinity G]"
                " [--ring-size R (default: %u)]"
                " [--run-cycles C (default: %d)]"
                " [--batch b]"
                " [--outstanding o]"
                " [--param p]"
                " [--sleep]"
                " [--relax]"
                " [--exit]"
                "\n",
                ring_size,
                runcycles);
}

int main(int argc, char **argv)
{
        int ret;
        pthread_t host, guest;
        void *tret;
        char *host_arg = NULL;
        char *guest_arg = NULL;
        char *endptr;
        long int c;

        kickfd = eventfd(0, 0);
        assert(kickfd >= 0);
        callfd = eventfd(0, 0);
        assert(callfd >= 0);

        for (;;) {
                int o = getopt_long(argc, argv, optstring, longopts, NULL);
                switch (o) {
                case -1:
                        goto done;
                case '?':
                        help();
                        exit(2);
                case 'H':
                        host_arg = optarg;
                        break;
                case 'G':
                        guest_arg = optarg;
                        break;
                case 'R':
                        ring_size = strtol(optarg, &endptr, 0);
                        assert(ring_size && !(ring_size & (ring_size - 1)));
                        assert(!*endptr);
                        break;
                case 'C':
                        c = strtol(optarg, &endptr, 0);
                        assert(!*endptr);
                        assert(c > 0 && c < INT_MAX);
                        runcycles = c;
                        break;
                case 'o':
                        c = strtol(optarg, &endptr, 0);
                        assert(!*endptr);
                        assert(c > 0 && c < INT_MAX);
                        max_outstanding = c;
                        break;
                case 'p':
                        c = strtol(optarg, &endptr, 0);
                        assert(!*endptr);
                        assert(c > 0 && c < INT_MAX);
                        param = c;
                        break;
                case 'b':
                        c = strtol(optarg, &endptr, 0);
                        assert(!*endptr);
                        assert(c > 0 && c < INT_MAX);
                        batch = c;
                        break;
                case 's':
                        do_sleep = true;
                        break;
                case 'x':
                        do_relax = true;
                        break;
                case 'e':
                        do_exit = true;
                        break;
                default:
                        help();
                        exit(4);
                        break;
                }
        }

        /* does nothing here, used to make sure all smp APIs compile */
        smp_acquire();
        smp_release();
        smp_mb();
done:

        if (batch > max_outstanding)
                batch = max_outstanding;

        if (optind < argc) {
                help();
                exit(4);
        }
        alloc_ring();

        ret = pthread_create(&host, NULL, start_host, host_arg);
        assert(!ret);
        ret = pthread_create(&guest, NULL, start_guest, guest_arg);
        assert(!ret);

        ret = pthread_join(guest, &tret);
        assert(!ret);
        ret = pthread_join(host, &tret);
        assert(!ret);
        return 0;
}