ivshmem-test.c 11.9 KB
Newer Older
A
Andreas Färber 已提交
1 2 3 4
/*
 * QTest testcase for ivshmem
 *
 * Copyright (c) 2014 SUSE LINUX Products GmbH
M
Marc-André Lureau 已提交
5
 * Copyright (c) 2015 Red Hat, Inc.
A
Andreas Färber 已提交
6 7 8 9 10
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

P
Peter Maydell 已提交
11
#include "qemu/osdep.h"
M
Marc-André Lureau 已提交
12 13
#include <glib/gstdio.h>
#include "contrib/ivshmem-server/ivshmem-server.h"
14
#include "libqos/libqos-pc.h"
15
#include "libqos/libqos-spapr.h"
A
Andreas Färber 已提交
16
#include "libqtest.h"
M
Marc-André Lureau 已提交
17
#include "qemu-common.h"
A
Andreas Färber 已提交
18

M
Marc-André Lureau 已提交
19 20 21 22 23
#define TMPSHMSIZE (1 << 20)
static char *tmpshm;
static void *tmpshmem;
static char *tmpdir;
static char *tmpserver;
A
Andreas Färber 已提交
24

M
Marc-André Lureau 已提交
25
static void save_fn(QPCIDevice *dev, int devfn, void *data)
A
Andreas Färber 已提交
26
{
M
Marc-André Lureau 已提交
27 28 29 30 31
    QPCIDevice **pdev = (QPCIDevice **) data;

    *pdev = dev;
}

M
Marc-André Lureau 已提交
32
static QPCIDevice *get_device(QPCIBus *pcibus)
M
Marc-André Lureau 已提交
33 34 35
{
    QPCIDevice *dev;

36
    dev = NULL;
M
Marc-André Lureau 已提交
37 38 39 40 41 42 43
    qpci_device_foreach(pcibus, 0x1af4, 0x1110, save_fn, &dev);
    g_assert(dev != NULL);

    return dev;
}

typedef struct _IVState {
44
    QOSState *qs;
45
    QPCIBar reg_bar, mem_bar;
M
Marc-André Lureau 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    QPCIDevice *dev;
} IVState;

enum Reg {
    INTRMASK = 0,
    INTRSTATUS = 4,
    IVPOSITION = 8,
    DOORBELL = 12,
};

static const char* reg2str(enum Reg reg) {
    switch (reg) {
    case INTRMASK:
        return "IntrMask";
    case INTRSTATUS:
        return "IntrStatus";
    case IVPOSITION:
        return "IVPosition";
    case DOORBELL:
        return "DoorBell";
    default:
        return NULL;
    }
}

static inline unsigned in_reg(IVState *s, enum Reg reg)
{
    const char *name = reg2str(reg);
    unsigned res;

76
    res = qpci_io_readl(s->dev, s->reg_bar, reg);
77
    g_test_message("*%s -> %x", name, res);
M
Marc-André Lureau 已提交
78 79 80 81 82 83 84 85

    return res;
}

static inline void out_reg(IVState *s, enum Reg reg, unsigned v)
{
    const char *name = reg2str(reg);

86
    g_test_message("%x -> *%s", v, name);
87
    qpci_io_writel(s->dev, s->reg_bar, reg, v);
M
Marc-André Lureau 已提交
88 89
}

90 91
static inline void read_mem(IVState *s, uint64_t off, void *buf, size_t len)
{
92
    qpci_memread(s->dev, s->mem_bar, off, buf, len);
93 94 95 96 97
}

static inline void write_mem(IVState *s, uint64_t off,
                             const void *buf, size_t len)
{
98
    qpci_memwrite(s->dev, s->mem_bar, off, buf, len);
99 100
}

M
Marc-André Lureau 已提交
101 102
static void cleanup_vm(IVState *s)
{
103
    assert(!global_qtest);
M
Marc-André Lureau 已提交
104
    g_free(s->dev);
105
    qtest_shutdown(s->qs);
M
Marc-André Lureau 已提交
106 107
}

M
Marc-André Lureau 已提交
108 109 110
static void setup_vm_cmd(IVState *s, const char *cmd, bool msix)
{
    uint64_t barsize;
111
    const char *arch = qtest_get_arch();
M
Marc-André Lureau 已提交
112

113 114
    if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
        s->qs = qtest_pc_boot(cmd);
115 116
    } else if (strcmp(arch, "ppc64") == 0) {
        s->qs = qtest_spapr_boot(cmd);
117
    } else {
118
        g_printerr("ivshmem-test tests are only available on x86 or ppc64\n");
119 120 121
        exit(EXIT_FAILURE);
    }
    s->dev = get_device(s->qs->pcibus);
M
Marc-André Lureau 已提交
122

123
    s->reg_bar = qpci_iomap(s->dev, 0, &barsize);
124
    g_assert_cmpuint(barsize, ==, 256);
M
Marc-André Lureau 已提交
125 126 127 128 129

    if (msix) {
        qpci_msix_enable(s->dev);
    }

130
    s->mem_bar = qpci_iomap(s->dev, 2, &barsize);
131
    g_assert_cmpuint(barsize, ==, TMPSHMSIZE);
M
Marc-André Lureau 已提交
132 133 134 135 136 137

    qpci_device_enable(s->dev);
}

static void setup_vm(IVState *s)
{
138 139 140
    char *cmd = g_strdup_printf("-object memory-backend-file"
                                ",id=mb1,size=1M,share,mem-path=/dev/shm%s"
                                " -device ivshmem-plain,memdev=mb1", tmpshm);
M
Marc-André Lureau 已提交
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

    setup_vm_cmd(s, cmd, false);

    g_free(cmd);
}

static void test_ivshmem_single(void)
{
    IVState state, *s;
    uint32_t data[1024];
    int i;

    setup_vm(&state);
    s = &state;

156 157 158 159
    /* initial state of readable registers */
    g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0);
    g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
    g_assert_cmpuint(in_reg(s, IVPOSITION), ==, 0);
M
Marc-André Lureau 已提交
160

161
    /* trigger interrupt via registers */
M
Marc-André Lureau 已提交
162 163 164
    out_reg(s, INTRMASK, 0xffffffff);
    g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0xffffffff);
    out_reg(s, INTRSTATUS, 1);
165
    /* check interrupt status */
M
Marc-André Lureau 已提交
166
    g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 1);
167 168 169
    /* reading clears */
    g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
    /* TODO intercept actual interrupt (needs qtest work) */
M
Marc-André Lureau 已提交
170

171
    /* invalid register access */
M
Marc-André Lureau 已提交
172
    out_reg(s, IVPOSITION, 1);
173 174 175
    in_reg(s, DOORBELL);

    /* ring the (non-functional) doorbell */
M
Marc-André Lureau 已提交
176 177
    out_reg(s, DOORBELL, 8 << 16);

178
    /* write shared memory */
M
Marc-André Lureau 已提交
179 180 181
    for (i = 0; i < G_N_ELEMENTS(data); i++) {
        data[i] = i;
    }
182
    write_mem(s, 0, data, sizeof(data));
M
Marc-André Lureau 已提交
183

184
    /* verify write */
M
Marc-André Lureau 已提交
185 186 187 188
    for (i = 0; i < G_N_ELEMENTS(data); i++) {
        g_assert_cmpuint(((uint32_t *)tmpshmem)[i], ==, i);
    }

189
    /* read it back and verify read */
M
Marc-André Lureau 已提交
190
    memset(data, 0, sizeof(data));
191
    read_mem(s, 0, data, sizeof(data));
M
Marc-André Lureau 已提交
192 193 194 195
    for (i = 0; i < G_N_ELEMENTS(data); i++) {
        g_assert_cmpuint(data[i], ==, i);
    }

M
Marc-André Lureau 已提交
196
    cleanup_vm(s);
M
Marc-André Lureau 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
}

static void test_ivshmem_pair(void)
{
    IVState state1, state2, *s1, *s2;
    char *data;
    int i;

    setup_vm(&state1);
    s1 = &state1;
    setup_vm(&state2);
    s2 = &state2;

    data = g_malloc0(TMPSHMSIZE);

    /* host write, guest 1 & 2 read */
    memset(tmpshmem, 0x42, TMPSHMSIZE);
214
    read_mem(s1, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
215 216 217
    for (i = 0; i < TMPSHMSIZE; i++) {
        g_assert_cmpuint(data[i], ==, 0x42);
    }
218
    read_mem(s2, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
219 220 221 222 223 224
    for (i = 0; i < TMPSHMSIZE; i++) {
        g_assert_cmpuint(data[i], ==, 0x42);
    }

    /* guest 1 write, guest 2 read */
    memset(data, 0x43, TMPSHMSIZE);
225
    write_mem(s1, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
226
    memset(data, 0, TMPSHMSIZE);
227
    read_mem(s2, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
228 229 230 231 232 233
    for (i = 0; i < TMPSHMSIZE; i++) {
        g_assert_cmpuint(data[i], ==, 0x43);
    }

    /* guest 2 write, guest 1 read */
    memset(data, 0x44, TMPSHMSIZE);
234
    write_mem(s2, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
235
    memset(data, 0, TMPSHMSIZE);
236
    read_mem(s1, 0, data, TMPSHMSIZE);
M
Marc-André Lureau 已提交
237 238 239 240
    for (i = 0; i < TMPSHMSIZE; i++) {
        g_assert_cmpuint(data[i], ==, 0x44);
    }

M
Marc-André Lureau 已提交
241 242
    cleanup_vm(s1);
    cleanup_vm(s2);
M
Marc-André Lureau 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
    g_free(data);
}

typedef struct ServerThread {
    GThread *thread;
    IvshmemServer *server;
    int pipe[2]; /* to handle quit */
} ServerThread;

static void *server_thread(void *data)
{
    ServerThread *t = data;
    IvshmemServer *server = t->server;

    while (true) {
        fd_set fds;
        int maxfd, ret;

        FD_ZERO(&fds);
        FD_SET(t->pipe[0], &fds);
        maxfd = t->pipe[0] + 1;

        ivshmem_server_get_fds(server, &fds, &maxfd);

        ret = select(maxfd, &fds, NULL, NULL, NULL);

        if (ret < 0) {
            if (errno == EINTR) {
                continue;
            }

            g_critical("select error: %s\n", strerror(errno));
            break;
        }
        if (ret == 0) {
            continue;
        }

        if (FD_ISSET(t->pipe[0], &fds)) {
            break;
        }

        if (ivshmem_server_handle_fds(server, &fds, maxfd) < 0) {
            g_critical("ivshmem_server_handle_fds() failed\n");
            break;
        }
    }

    return NULL;
}

294
static void setup_vm_with_server(IVState *s, int nvectors)
M
Marc-André Lureau 已提交
295
{
296
    char *cmd;
M
Marc-André Lureau 已提交
297

298
    cmd = g_strdup_printf("-chardev socket,id=chr0,path=%s "
299 300 301 302
                          "-device ivshmem-doorbell,chardev=chr0,vectors=%d",
                          tmpserver, nvectors);

    setup_vm_cmd(s, cmd, true);
M
Marc-André Lureau 已提交
303 304 305 306

    g_free(cmd);
}

307
static void test_ivshmem_server(void)
M
Marc-André Lureau 已提交
308 309 310 311 312 313 314 315
{
    IVState state1, state2, *s1, *s2;
    ServerThread thread;
    IvshmemServer server;
    int ret, vm1, vm2;
    int nvectors = 2;
    guint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;

316
    ret = ivshmem_server_init(&server, tmpserver, tmpshm, true,
M
Marc-André Lureau 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329
                              TMPSHMSIZE, nvectors,
                              g_test_verbose());
    g_assert_cmpint(ret, ==, 0);

    ret = ivshmem_server_start(&server);
    g_assert_cmpint(ret, ==, 0);

    thread.server = &server;
    ret = pipe(thread.pipe);
    g_assert_cmpint(ret, ==, 0);
    thread.thread = g_thread_new("ivshmem-server", server_thread, &thread);
    g_assert(thread.thread != NULL);

330
    setup_vm_with_server(&state1, nvectors);
331
    s1 = &state1;
332
    setup_vm_with_server(&state2, nvectors);
333
    s2 = &state2;
M
Marc-André Lureau 已提交
334 335 336 337

    /* check got different VM ids */
    vm1 = in_reg(s1, IVPOSITION);
    vm2 = in_reg(s2, IVPOSITION);
338 339 340
    g_assert_cmpint(vm1, >=, 0);
    g_assert_cmpint(vm2, >=, 0);
    g_assert_cmpint(vm1, !=, vm2);
M
Marc-André Lureau 已提交
341

342
    /* check number of MSI-X vectors */
343 344
    ret = qpci_msix_table_size(s1->dev);
    g_assert_cmpuint(ret, ==, nvectors);
M
Marc-André Lureau 已提交
345

346 347 348
    /* TODO test behavior before MSI-X is enabled */

    /* ping vm2 -> vm1 on vector 0 */
349 350
    ret = qpci_msix_pending(s1->dev, 0);
    g_assert_cmpuint(ret, ==, 0);
M
Marc-André Lureau 已提交
351 352 353
    out_reg(s2, DOORBELL, vm1 << 16);
    do {
        g_usleep(10000);
354
        ret = qpci_msix_pending(s1->dev, 0);
M
Marc-André Lureau 已提交
355 356 357
    } while (ret == 0 && g_get_monotonic_time() < end_time);
    g_assert_cmpuint(ret, !=, 0);

358
    /* ping vm1 -> vm2 on vector 1 */
359 360
    ret = qpci_msix_pending(s2->dev, 1);
    g_assert_cmpuint(ret, ==, 0);
361
    out_reg(s1, DOORBELL, vm2 << 16 | 1);
M
Marc-André Lureau 已提交
362 363
    do {
        g_usleep(10000);
364
        ret = qpci_msix_pending(s2->dev, 1);
M
Marc-André Lureau 已提交
365 366 367
    } while (ret == 0 && g_get_monotonic_time() < end_time);
    g_assert_cmpuint(ret, !=, 0);

M
Marc-André Lureau 已提交
368 369
    cleanup_vm(s2);
    cleanup_vm(s1);
M
Marc-André Lureau 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

    if (qemu_write_full(thread.pipe[1], "q", 1) != 1) {
        g_error("qemu_write_full: %s", g_strerror(errno));
    }

    g_thread_join(thread.thread);

    ivshmem_server_close(&server);
    close(thread.pipe[1]);
    close(thread.pipe[0]);
}

#define PCI_SLOT_HP             0x06

static void test_ivshmem_hotplug(void)
{
386
    const char *arch = qtest_get_arch();
M
Marc-André Lureau 已提交
387

388
    qtest_start("-object memory-backend-ram,size=1M,id=mb1");
M
Marc-André Lureau 已提交
389

390 391 392
    qtest_qmp_device_add("ivshmem-plain", "iv1",
                         "{'addr': %s, 'memdev': 'mb1'}",
                         stringify(PCI_SLOT_HP));
393 394 395
    if (strcmp(arch, "ppc64") != 0) {
        qpci_unplug_acpi_device_test("iv1", PCI_SLOT_HP);
    }
M
Marc-André Lureau 已提交
396 397 398 399

    qtest_end();
}

M
Marc-André Lureau 已提交
400 401 402 403 404 405
static void test_ivshmem_memdev(void)
{
    IVState state;

    /* just for the sake of checking memory-backend property */
    setup_vm_cmd(&state, "-object memory-backend-ram,size=1M,id=mb1"
406
                 " -device ivshmem-plain,memdev=mb1", false);
M
Marc-André Lureau 已提交
407

M
Marc-André Lureau 已提交
408
    cleanup_vm(&state);
M
Marc-André Lureau 已提交
409 410
}

M
Marc-André Lureau 已提交
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
static void cleanup(void)
{
    if (tmpshmem) {
        munmap(tmpshmem, TMPSHMSIZE);
        tmpshmem = NULL;
    }

    if (tmpshm) {
        shm_unlink(tmpshm);
        g_free(tmpshm);
        tmpshm = NULL;
    }

    if (tmpserver) {
        g_unlink(tmpserver);
        g_free(tmpserver);
        tmpserver = NULL;
    }

    if (tmpdir) {
        g_rmdir(tmpdir);
        tmpdir = NULL;
    }
}

static void abrt_handler(void *data)
{
    cleanup();
}

static gchar *mktempshm(int size, int *fd)
{
    while (true) {
        gchar *name;

        name = g_strdup_printf("/qtest-%u-%u", getpid(), g_random_int());
        *fd = shm_open(name, O_CREAT|O_RDWR|O_EXCL,
                       S_IRWXU|S_IRWXG|S_IRWXO);
        if (*fd > 0) {
            g_assert(ftruncate(*fd, size) == 0);
            return name;
        }

        g_free(name);

        if (errno != EEXIST) {
            perror("shm_open");
            return NULL;
        }
    }
A
Andreas Färber 已提交
461 462 463 464 465
}

int main(int argc, char **argv)
{
    int ret, fd;
466
    const char *arch = qtest_get_arch();
M
Marc-André Lureau 已提交
467 468
    gchar dir[] = "/tmp/ivshmem-test.XXXXXX";

A
Andreas Färber 已提交
469 470
    g_test_init(&argc, &argv, NULL);

M
Marc-André Lureau 已提交
471 472 473 474
    qtest_add_abrt_handler(abrt_handler, NULL);
    /* shm */
    tmpshm = mktempshm(TMPSHMSIZE, &fd);
    if (!tmpshm) {
475
        goto out;
M
Marc-André Lureau 已提交
476 477 478 479 480 481 482 483 484
    }
    tmpshmem = mmap(0, TMPSHMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    g_assert(tmpshmem != MAP_FAILED);
    /* server */
    if (mkdtemp(dir) == NULL) {
        g_error("mkdtemp: %s", g_strerror(errno));
    }
    tmpdir = dir;
    tmpserver = g_strconcat(tmpdir, "/server", NULL);
A
Andreas Färber 已提交
485

M
Marc-André Lureau 已提交
486 487
    qtest_add_func("/ivshmem/single", test_ivshmem_single);
    qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug);
M
Marc-André Lureau 已提交
488
    qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev);
489 490
    if (g_test_slow()) {
        qtest_add_func("/ivshmem/pair", test_ivshmem_pair);
491
        if (strcmp(arch, "ppc64") != 0) {
492
            qtest_add_func("/ivshmem/server", test_ivshmem_server);
493
        }
494
    }
A
Andreas Färber 已提交
495

496
out:
A
Andreas Färber 已提交
497
    ret = g_test_run();
M
Marc-André Lureau 已提交
498
    cleanup();
A
Andreas Färber 已提交
499 500
    return ret;
}