qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

ivshmem-test.c (13049B)


      1 /*
      2  * QTest testcase for ivshmem
      3  *
      4  * Copyright (c) 2014 SUSE LINUX Products GmbH
      5  * Copyright (c) 2015 Red Hat, Inc.
      6  *
      7  * This work is licensed under the terms of the GNU GPL, version 2 or later.
      8  * See the COPYING file in the top-level directory.
      9  */
     10 
     11 #include "qemu/osdep.h"
     12 #include <glib/gstdio.h>
     13 #include "contrib/ivshmem-server/ivshmem-server.h"
     14 #include "libqos/libqos-pc.h"
     15 #include "libqos/libqos-spapr.h"
     16 #include "libqtest.h"
     17 
     18 #define TMPSHMSIZE (1 << 20)
     19 static char *tmpshm;
     20 static void *tmpshmem;
     21 static char *tmpdir;
     22 static char *tmpserver;
     23 
     24 static void save_fn(QPCIDevice *dev, int devfn, void *data)
     25 {
     26     QPCIDevice **pdev = (QPCIDevice **) data;
     27 
     28     *pdev = dev;
     29 }
     30 
     31 static QPCIDevice *get_device(QPCIBus *pcibus)
     32 {
     33     QPCIDevice *dev;
     34 
     35     dev = NULL;
     36     qpci_device_foreach(pcibus, 0x1af4, 0x1110, save_fn, &dev);
     37     g_assert(dev != NULL);
     38 
     39     return dev;
     40 }
     41 
     42 typedef struct _IVState {
     43     QOSState *qs;
     44     QPCIBar reg_bar, mem_bar;
     45     QPCIDevice *dev;
     46 } IVState;
     47 
     48 enum Reg {
     49     INTRMASK = 0,
     50     INTRSTATUS = 4,
     51     IVPOSITION = 8,
     52     DOORBELL = 12,
     53 };
     54 
     55 static const char* reg2str(enum Reg reg) {
     56     switch (reg) {
     57     case INTRMASK:
     58         return "IntrMask";
     59     case INTRSTATUS:
     60         return "IntrStatus";
     61     case IVPOSITION:
     62         return "IVPosition";
     63     case DOORBELL:
     64         return "DoorBell";
     65     default:
     66         return NULL;
     67     }
     68 }
     69 
     70 static inline unsigned in_reg(IVState *s, enum Reg reg)
     71 {
     72     const char *name = reg2str(reg);
     73     unsigned res;
     74 
     75     res = qpci_io_readl(s->dev, s->reg_bar, reg);
     76     g_test_message("*%s -> %x", name, res);
     77 
     78     return res;
     79 }
     80 
     81 static inline void out_reg(IVState *s, enum Reg reg, unsigned v)
     82 {
     83     const char *name = reg2str(reg);
     84 
     85     g_test_message("%x -> *%s", v, name);
     86     qpci_io_writel(s->dev, s->reg_bar, reg, v);
     87 }
     88 
     89 static inline void read_mem(IVState *s, uint64_t off, void *buf, size_t len)
     90 {
     91     qpci_memread(s->dev, s->mem_bar, off, buf, len);
     92 }
     93 
     94 static inline void write_mem(IVState *s, uint64_t off,
     95                              const void *buf, size_t len)
     96 {
     97     qpci_memwrite(s->dev, s->mem_bar, off, buf, len);
     98 }
     99 
    100 static void cleanup_vm(IVState *s)
    101 {
    102     g_free(s->dev);
    103     qtest_shutdown(s->qs);
    104 }
    105 
    106 static void setup_vm_cmd(IVState *s, const char *cmd, bool msix)
    107 {
    108     uint64_t barsize;
    109     const char *arch = qtest_get_arch();
    110 
    111     if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
    112         s->qs = qtest_pc_boot(cmd);
    113     } else if (strcmp(arch, "ppc64") == 0) {
    114         s->qs = qtest_spapr_boot(cmd);
    115     } else {
    116         g_printerr("ivshmem-test tests are only available on x86 or ppc64\n");
    117         exit(EXIT_FAILURE);
    118     }
    119     s->dev = get_device(s->qs->pcibus);
    120 
    121     s->reg_bar = qpci_iomap(s->dev, 0, &barsize);
    122     g_assert_cmpuint(barsize, ==, 256);
    123 
    124     if (msix) {
    125         qpci_msix_enable(s->dev);
    126     }
    127 
    128     s->mem_bar = qpci_iomap(s->dev, 2, &barsize);
    129     g_assert_cmpuint(barsize, ==, TMPSHMSIZE);
    130 
    131     qpci_device_enable(s->dev);
    132 }
    133 
    134 static void setup_vm(IVState *s)
    135 {
    136     char *cmd = g_strdup_printf("-object memory-backend-file"
    137                                 ",id=mb1,size=1M,share=on,mem-path=/dev/shm%s"
    138                                 " -device ivshmem-plain,memdev=mb1", tmpshm);
    139 
    140     setup_vm_cmd(s, cmd, false);
    141 
    142     g_free(cmd);
    143 }
    144 
    145 static void test_ivshmem_single(void)
    146 {
    147     IVState state, *s;
    148     uint32_t data[1024];
    149     int i;
    150 
    151     setup_vm(&state);
    152     s = &state;
    153 
    154     /* initial state of readable registers */
    155     g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0);
    156     g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
    157     g_assert_cmpuint(in_reg(s, IVPOSITION), ==, 0);
    158 
    159     /* trigger interrupt via registers */
    160     out_reg(s, INTRMASK, 0xffffffff);
    161     g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0xffffffff);
    162     out_reg(s, INTRSTATUS, 1);
    163     /* check interrupt status */
    164     g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 1);
    165     /* reading clears */
    166     g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
    167     /* TODO intercept actual interrupt (needs qtest work) */
    168 
    169     /* invalid register access */
    170     out_reg(s, IVPOSITION, 1);
    171     in_reg(s, DOORBELL);
    172 
    173     /* ring the (non-functional) doorbell */
    174     out_reg(s, DOORBELL, 8 << 16);
    175 
    176     /* write shared memory */
    177     for (i = 0; i < G_N_ELEMENTS(data); i++) {
    178         data[i] = i;
    179     }
    180     write_mem(s, 0, data, sizeof(data));
    181 
    182     /* verify write */
    183     for (i = 0; i < G_N_ELEMENTS(data); i++) {
    184         g_assert_cmpuint(((uint32_t *)tmpshmem)[i], ==, i);
    185     }
    186 
    187     /* read it back and verify read */
    188     memset(data, 0, sizeof(data));
    189     read_mem(s, 0, data, sizeof(data));
    190     for (i = 0; i < G_N_ELEMENTS(data); i++) {
    191         g_assert_cmpuint(data[i], ==, i);
    192     }
    193 
    194     cleanup_vm(s);
    195 }
    196 
    197 static void test_ivshmem_pair(void)
    198 {
    199     IVState state1, state2, *s1, *s2;
    200     char *data;
    201     int i;
    202 
    203     setup_vm(&state1);
    204     s1 = &state1;
    205     setup_vm(&state2);
    206     s2 = &state2;
    207 
    208     data = g_malloc0(TMPSHMSIZE);
    209 
    210     /* host write, guest 1 & 2 read */
    211     memset(tmpshmem, 0x42, TMPSHMSIZE);
    212     read_mem(s1, 0, data, TMPSHMSIZE);
    213     for (i = 0; i < TMPSHMSIZE; i++) {
    214         g_assert_cmpuint(data[i], ==, 0x42);
    215     }
    216     read_mem(s2, 0, data, TMPSHMSIZE);
    217     for (i = 0; i < TMPSHMSIZE; i++) {
    218         g_assert_cmpuint(data[i], ==, 0x42);
    219     }
    220 
    221     /* guest 1 write, guest 2 read */
    222     memset(data, 0x43, TMPSHMSIZE);
    223     write_mem(s1, 0, data, TMPSHMSIZE);
    224     memset(data, 0, TMPSHMSIZE);
    225     read_mem(s2, 0, data, TMPSHMSIZE);
    226     for (i = 0; i < TMPSHMSIZE; i++) {
    227         g_assert_cmpuint(data[i], ==, 0x43);
    228     }
    229 
    230     /* guest 2 write, guest 1 read */
    231     memset(data, 0x44, TMPSHMSIZE);
    232     write_mem(s2, 0, data, TMPSHMSIZE);
    233     memset(data, 0, TMPSHMSIZE);
    234     read_mem(s1, 0, data, TMPSHMSIZE);
    235     for (i = 0; i < TMPSHMSIZE; i++) {
    236         g_assert_cmpuint(data[i], ==, 0x44);
    237     }
    238 
    239     cleanup_vm(s1);
    240     cleanup_vm(s2);
    241     g_free(data);
    242 }
    243 
    244 typedef struct ServerThread {
    245     GThread *thread;
    246     IvshmemServer *server;
    247     int pipe[2]; /* to handle quit */
    248 } ServerThread;
    249 
    250 static void *server_thread(void *data)
    251 {
    252     ServerThread *t = data;
    253     IvshmemServer *server = t->server;
    254 
    255     while (true) {
    256         fd_set fds;
    257         int maxfd, ret;
    258 
    259         FD_ZERO(&fds);
    260         FD_SET(t->pipe[0], &fds);
    261         maxfd = t->pipe[0] + 1;
    262 
    263         ivshmem_server_get_fds(server, &fds, &maxfd);
    264 
    265         ret = select(maxfd, &fds, NULL, NULL, NULL);
    266 
    267         if (ret < 0) {
    268             if (errno == EINTR) {
    269                 continue;
    270             }
    271 
    272             g_critical("select error: %s\n", strerror(errno));
    273             break;
    274         }
    275         if (ret == 0) {
    276             continue;
    277         }
    278 
    279         if (FD_ISSET(t->pipe[0], &fds)) {
    280             break;
    281         }
    282 
    283         if (ivshmem_server_handle_fds(server, &fds, maxfd) < 0) {
    284             g_critical("ivshmem_server_handle_fds() failed\n");
    285             break;
    286         }
    287     }
    288 
    289     return NULL;
    290 }
    291 
    292 static void setup_vm_with_server(IVState *s, int nvectors)
    293 {
    294     char *cmd;
    295 
    296     cmd = g_strdup_printf("-chardev socket,id=chr0,path=%s "
    297                           "-device ivshmem-doorbell,chardev=chr0,vectors=%d",
    298                           tmpserver, nvectors);
    299 
    300     setup_vm_cmd(s, cmd, true);
    301 
    302     g_free(cmd);
    303 }
    304 
    305 static void test_ivshmem_server(void)
    306 {
    307     g_autoptr(GError) err = NULL;
    308     IVState state1, state2, *s1, *s2;
    309     ServerThread thread;
    310     IvshmemServer server;
    311     int ret, vm1, vm2;
    312     int nvectors = 2;
    313     guint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
    314 
    315     ret = ivshmem_server_init(&server, tmpserver, tmpshm, true,
    316                               TMPSHMSIZE, nvectors,
    317                               g_test_verbose());
    318     g_assert_cmpint(ret, ==, 0);
    319 
    320     ret = ivshmem_server_start(&server);
    321     g_assert_cmpint(ret, ==, 0);
    322 
    323     thread.server = &server;
    324     g_unix_open_pipe(thread.pipe, FD_CLOEXEC, &err);
    325     g_assert_no_error(err);
    326     thread.thread = g_thread_new("ivshmem-server", server_thread, &thread);
    327     g_assert(thread.thread != NULL);
    328 
    329     setup_vm_with_server(&state1, nvectors);
    330     s1 = &state1;
    331     setup_vm_with_server(&state2, nvectors);
    332     s2 = &state2;
    333 
    334     /* check got different VM ids */
    335     vm1 = in_reg(s1, IVPOSITION);
    336     vm2 = in_reg(s2, IVPOSITION);
    337     g_assert_cmpint(vm1, >=, 0);
    338     g_assert_cmpint(vm2, >=, 0);
    339     g_assert_cmpint(vm1, !=, vm2);
    340 
    341     /* check number of MSI-X vectors */
    342     ret = qpci_msix_table_size(s1->dev);
    343     g_assert_cmpuint(ret, ==, nvectors);
    344 
    345     /* TODO test behavior before MSI-X is enabled */
    346 
    347     /* ping vm2 -> vm1 on vector 0 */
    348     ret = qpci_msix_pending(s1->dev, 0);
    349     g_assert_cmpuint(ret, ==, 0);
    350     out_reg(s2, DOORBELL, vm1 << 16);
    351     do {
    352         g_usleep(10000);
    353         ret = qpci_msix_pending(s1->dev, 0);
    354     } while (ret == 0 && g_get_monotonic_time() < end_time);
    355     g_assert_cmpuint(ret, !=, 0);
    356 
    357     /* ping vm1 -> vm2 on vector 1 */
    358     ret = qpci_msix_pending(s2->dev, 1);
    359     g_assert_cmpuint(ret, ==, 0);
    360     out_reg(s1, DOORBELL, vm2 << 16 | 1);
    361     do {
    362         g_usleep(10000);
    363         ret = qpci_msix_pending(s2->dev, 1);
    364     } while (ret == 0 && g_get_monotonic_time() < end_time);
    365     g_assert_cmpuint(ret, !=, 0);
    366 
    367     cleanup_vm(s2);
    368     cleanup_vm(s1);
    369 
    370     if (qemu_write_full(thread.pipe[1], "q", 1) != 1) {
    371         g_error("qemu_write_full: %s", g_strerror(errno));
    372     }
    373 
    374     g_thread_join(thread.thread);
    375 
    376     ivshmem_server_close(&server);
    377     close(thread.pipe[1]);
    378     close(thread.pipe[0]);
    379 }
    380 
    381 static void test_ivshmem_hotplug_q35(void)
    382 {
    383     QTestState *qts = qtest_init("-object memory-backend-ram,size=1M,id=mb1 "
    384                                  "-device pcie-root-port,id=p1 "
    385                                  "-device pcie-pci-bridge,bus=p1,id=b1 "
    386                                  "-machine q35");
    387 
    388     qtest_qmp_device_add(qts, "ivshmem-plain", "iv1",
    389                          "{'memdev': 'mb1', 'bus': 'b1'}");
    390     qtest_qmp_device_del_send(qts, "iv1");
    391 
    392     qtest_quit(qts);
    393 }
    394 
    395 #define PCI_SLOT_HP             0x06
    396 
    397 static void test_ivshmem_hotplug(void)
    398 {
    399     QTestState *qts;
    400     const char *arch = qtest_get_arch();
    401 
    402     if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
    403         qts = qtest_init("-object memory-backend-ram,size=1M,id=mb1"
    404                          " -machine pc");
    405     } else {
    406         qts = qtest_init("-object memory-backend-ram,size=1M,id=mb1");
    407     }
    408 
    409     qtest_qmp_device_add(qts, "ivshmem-plain", "iv1",
    410                          "{'addr': %s, 'memdev': 'mb1'}",
    411                          stringify(PCI_SLOT_HP));
    412     if (strcmp(arch, "ppc64") != 0) {
    413         qpci_unplug_acpi_device_test(qts, "iv1", PCI_SLOT_HP);
    414     }
    415 
    416     qtest_quit(qts);
    417 }
    418 
    419 static void test_ivshmem_memdev(void)
    420 {
    421     IVState state;
    422 
    423     /* just for the sake of checking memory-backend property */
    424     setup_vm_cmd(&state, "-object memory-backend-ram,size=1M,id=mb1"
    425                  " -device ivshmem-plain,memdev=mb1", false);
    426 
    427     cleanup_vm(&state);
    428 }
    429 
    430 static void cleanup(void)
    431 {
    432     if (tmpshmem) {
    433         munmap(tmpshmem, TMPSHMSIZE);
    434         tmpshmem = NULL;
    435     }
    436 
    437     if (tmpshm) {
    438         shm_unlink(tmpshm);
    439         g_free(tmpshm);
    440         tmpshm = NULL;
    441     }
    442 
    443     if (tmpserver) {
    444         g_unlink(tmpserver);
    445         g_free(tmpserver);
    446         tmpserver = NULL;
    447     }
    448 
    449     if (tmpdir) {
    450         g_rmdir(tmpdir);
    451         tmpdir = NULL;
    452     }
    453 }
    454 
    455 static void abrt_handler(void *data)
    456 {
    457     cleanup();
    458 }
    459 
    460 static gchar *mktempshm(int size, int *fd)
    461 {
    462     while (true) {
    463         gchar *name;
    464 
    465         name = g_strdup_printf("/qtest-%u-%u", getpid(), g_test_rand_int());
    466         *fd = shm_open(name, O_CREAT|O_RDWR|O_EXCL,
    467                        S_IRWXU|S_IRWXG|S_IRWXO);
    468         if (*fd > 0) {
    469             g_assert(ftruncate(*fd, size) == 0);
    470             return name;
    471         }
    472 
    473         g_free(name);
    474 
    475         if (errno != EEXIST) {
    476             perror("shm_open");
    477             return NULL;
    478         }
    479     }
    480 }
    481 
    482 int main(int argc, char **argv)
    483 {
    484     int ret, fd;
    485     gchar dir[] = "/tmp/ivshmem-test.XXXXXX";
    486     const char *arch = qtest_get_arch();
    487 
    488     g_test_init(&argc, &argv, NULL);
    489 
    490     qtest_add_abrt_handler(abrt_handler, NULL);
    491     /* shm */
    492     tmpshm = mktempshm(TMPSHMSIZE, &fd);
    493     if (!tmpshm) {
    494         goto out;
    495     }
    496     tmpshmem = mmap(0, TMPSHMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    497     g_assert(tmpshmem != MAP_FAILED);
    498     /* server */
    499     if (g_mkdtemp(dir) == NULL) {
    500         g_error("g_mkdtemp: %s", g_strerror(errno));
    501     }
    502     tmpdir = dir;
    503     tmpserver = g_strconcat(tmpdir, "/server", NULL);
    504 
    505     qtest_add_func("/ivshmem/single", test_ivshmem_single);
    506     qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug);
    507     qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev);
    508     if (g_test_slow()) {
    509         qtest_add_func("/ivshmem/pair", test_ivshmem_pair);
    510         qtest_add_func("/ivshmem/server", test_ivshmem_server);
    511     }
    512     if (!strcmp(arch, "x86_64") && qtest_has_machine("q35")) {
    513         qtest_add_func("/ivshmem/hotplug-q35", test_ivshmem_hotplug_q35);
    514     }
    515 
    516 out:
    517     ret = g_test_run();
    518     cleanup();
    519     return ret;
    520 }