ivshmem-test.c (12241B)
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 "libqos/libqtest.h" 17#include "qemu-common.h" 18 19#define TMPSHMSIZE (1 << 20) 20static char *tmpshm; 21static void *tmpshmem; 22static char *tmpdir; 23static char *tmpserver; 24 25static void save_fn(QPCIDevice *dev, int devfn, void *data) 26{ 27 QPCIDevice **pdev = (QPCIDevice **) data; 28 29 *pdev = dev; 30} 31 32static QPCIDevice *get_device(QPCIBus *pcibus) 33{ 34 QPCIDevice *dev; 35 36 dev = NULL; 37 qpci_device_foreach(pcibus, 0x1af4, 0x1110, save_fn, &dev); 38 g_assert(dev != NULL); 39 40 return dev; 41} 42 43typedef struct _IVState { 44 QOSState *qs; 45 QPCIBar reg_bar, mem_bar; 46 QPCIDevice *dev; 47} IVState; 48 49enum Reg { 50 INTRMASK = 0, 51 INTRSTATUS = 4, 52 IVPOSITION = 8, 53 DOORBELL = 12, 54}; 55 56static const char* reg2str(enum Reg reg) { 57 switch (reg) { 58 case INTRMASK: 59 return "IntrMask"; 60 case INTRSTATUS: 61 return "IntrStatus"; 62 case IVPOSITION: 63 return "IVPosition"; 64 case DOORBELL: 65 return "DoorBell"; 66 default: 67 return NULL; 68 } 69} 70 71static inline unsigned in_reg(IVState *s, enum Reg reg) 72{ 73 const char *name = reg2str(reg); 74 unsigned res; 75 76 res = qpci_io_readl(s->dev, s->reg_bar, reg); 77 g_test_message("*%s -> %x", name, res); 78 79 return res; 80} 81 82static inline void out_reg(IVState *s, enum Reg reg, unsigned v) 83{ 84 const char *name = reg2str(reg); 85 86 g_test_message("%x -> *%s", v, name); 87 qpci_io_writel(s->dev, s->reg_bar, reg, v); 88} 89 90static inline void read_mem(IVState *s, uint64_t off, void *buf, size_t len) 91{ 92 qpci_memread(s->dev, s->mem_bar, off, buf, len); 93} 94 95static inline void write_mem(IVState *s, uint64_t off, 96 const void *buf, size_t len) 97{ 98 qpci_memwrite(s->dev, s->mem_bar, off, buf, len); 99} 100 101static void cleanup_vm(IVState *s) 102{ 103 g_free(s->dev); 104 qtest_shutdown(s->qs); 105} 106 107static void setup_vm_cmd(IVState *s, const char *cmd, bool msix) 108{ 109 uint64_t barsize; 110 const char *arch = qtest_get_arch(); 111 112 if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { 113 s->qs = qtest_pc_boot(cmd); 114 } else if (strcmp(arch, "ppc64") == 0) { 115 s->qs = qtest_spapr_boot(cmd); 116 } else { 117 g_printerr("ivshmem-test tests are only available on x86 or ppc64\n"); 118 exit(EXIT_FAILURE); 119 } 120 s->dev = get_device(s->qs->pcibus); 121 122 s->reg_bar = qpci_iomap(s->dev, 0, &barsize); 123 g_assert_cmpuint(barsize, ==, 256); 124 125 if (msix) { 126 qpci_msix_enable(s->dev); 127 } 128 129 s->mem_bar = qpci_iomap(s->dev, 2, &barsize); 130 g_assert_cmpuint(barsize, ==, TMPSHMSIZE); 131 132 qpci_device_enable(s->dev); 133} 134 135static void setup_vm(IVState *s) 136{ 137 char *cmd = g_strdup_printf("-object memory-backend-file" 138 ",id=mb1,size=1M,share=on,mem-path=/dev/shm%s" 139 " -device ivshmem-plain,memdev=mb1", tmpshm); 140 141 setup_vm_cmd(s, cmd, false); 142 143 g_free(cmd); 144} 145 146static void test_ivshmem_single(void) 147{ 148 IVState state, *s; 149 uint32_t data[1024]; 150 int i; 151 152 setup_vm(&state); 153 s = &state; 154 155 /* initial state of readable registers */ 156 g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0); 157 g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0); 158 g_assert_cmpuint(in_reg(s, IVPOSITION), ==, 0); 159 160 /* trigger interrupt via registers */ 161 out_reg(s, INTRMASK, 0xffffffff); 162 g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0xffffffff); 163 out_reg(s, INTRSTATUS, 1); 164 /* check interrupt status */ 165 g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 1); 166 /* reading clears */ 167 g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0); 168 /* TODO intercept actual interrupt (needs qtest work) */ 169 170 /* invalid register access */ 171 out_reg(s, IVPOSITION, 1); 172 in_reg(s, DOORBELL); 173 174 /* ring the (non-functional) doorbell */ 175 out_reg(s, DOORBELL, 8 << 16); 176 177 /* write shared memory */ 178 for (i = 0; i < G_N_ELEMENTS(data); i++) { 179 data[i] = i; 180 } 181 write_mem(s, 0, data, sizeof(data)); 182 183 /* verify write */ 184 for (i = 0; i < G_N_ELEMENTS(data); i++) { 185 g_assert_cmpuint(((uint32_t *)tmpshmem)[i], ==, i); 186 } 187 188 /* read it back and verify read */ 189 memset(data, 0, sizeof(data)); 190 read_mem(s, 0, data, sizeof(data)); 191 for (i = 0; i < G_N_ELEMENTS(data); i++) { 192 g_assert_cmpuint(data[i], ==, i); 193 } 194 195 cleanup_vm(s); 196} 197 198static void test_ivshmem_pair(void) 199{ 200 IVState state1, state2, *s1, *s2; 201 char *data; 202 int i; 203 204 setup_vm(&state1); 205 s1 = &state1; 206 setup_vm(&state2); 207 s2 = &state2; 208 209 data = g_malloc0(TMPSHMSIZE); 210 211 /* host write, guest 1 & 2 read */ 212 memset(tmpshmem, 0x42, TMPSHMSIZE); 213 read_mem(s1, 0, data, TMPSHMSIZE); 214 for (i = 0; i < TMPSHMSIZE; i++) { 215 g_assert_cmpuint(data[i], ==, 0x42); 216 } 217 read_mem(s2, 0, data, TMPSHMSIZE); 218 for (i = 0; i < TMPSHMSIZE; i++) { 219 g_assert_cmpuint(data[i], ==, 0x42); 220 } 221 222 /* guest 1 write, guest 2 read */ 223 memset(data, 0x43, TMPSHMSIZE); 224 write_mem(s1, 0, data, TMPSHMSIZE); 225 memset(data, 0, TMPSHMSIZE); 226 read_mem(s2, 0, data, TMPSHMSIZE); 227 for (i = 0; i < TMPSHMSIZE; i++) { 228 g_assert_cmpuint(data[i], ==, 0x43); 229 } 230 231 /* guest 2 write, guest 1 read */ 232 memset(data, 0x44, TMPSHMSIZE); 233 write_mem(s2, 0, data, TMPSHMSIZE); 234 memset(data, 0, TMPSHMSIZE); 235 read_mem(s1, 0, data, TMPSHMSIZE); 236 for (i = 0; i < TMPSHMSIZE; i++) { 237 g_assert_cmpuint(data[i], ==, 0x44); 238 } 239 240 cleanup_vm(s1); 241 cleanup_vm(s2); 242 g_free(data); 243} 244 245typedef struct ServerThread { 246 GThread *thread; 247 IvshmemServer *server; 248 int pipe[2]; /* to handle quit */ 249} ServerThread; 250 251static void *server_thread(void *data) 252{ 253 ServerThread *t = data; 254 IvshmemServer *server = t->server; 255 256 while (true) { 257 fd_set fds; 258 int maxfd, ret; 259 260 FD_ZERO(&fds); 261 FD_SET(t->pipe[0], &fds); 262 maxfd = t->pipe[0] + 1; 263 264 ivshmem_server_get_fds(server, &fds, &maxfd); 265 266 ret = select(maxfd, &fds, NULL, NULL, NULL); 267 268 if (ret < 0) { 269 if (errno == EINTR) { 270 continue; 271 } 272 273 g_critical("select error: %s\n", strerror(errno)); 274 break; 275 } 276 if (ret == 0) { 277 continue; 278 } 279 280 if (FD_ISSET(t->pipe[0], &fds)) { 281 break; 282 } 283 284 if (ivshmem_server_handle_fds(server, &fds, maxfd) < 0) { 285 g_critical("ivshmem_server_handle_fds() failed\n"); 286 break; 287 } 288 } 289 290 return NULL; 291} 292 293static void setup_vm_with_server(IVState *s, int nvectors) 294{ 295 char *cmd; 296 297 cmd = g_strdup_printf("-chardev socket,id=chr0,path=%s " 298 "-device ivshmem-doorbell,chardev=chr0,vectors=%d", 299 tmpserver, nvectors); 300 301 setup_vm_cmd(s, cmd, true); 302 303 g_free(cmd); 304} 305 306static void test_ivshmem_server(void) 307{ 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 ret = pipe(thread.pipe); 325 g_assert_cmpint(ret, ==, 0); 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#define PCI_SLOT_HP 0x06 382 383static void test_ivshmem_hotplug(void) 384{ 385 QTestState *qts; 386 const char *arch = qtest_get_arch(); 387 388 qts = qtest_init("-object memory-backend-ram,size=1M,id=mb1"); 389 390 qtest_qmp_device_add(qts, "ivshmem-plain", "iv1", 391 "{'addr': %s, 'memdev': 'mb1'}", 392 stringify(PCI_SLOT_HP)); 393 if (strcmp(arch, "ppc64") != 0) { 394 qpci_unplug_acpi_device_test(qts, "iv1", PCI_SLOT_HP); 395 } 396 397 qtest_quit(qts); 398} 399 400static void test_ivshmem_memdev(void) 401{ 402 IVState state; 403 404 /* just for the sake of checking memory-backend property */ 405 setup_vm_cmd(&state, "-object memory-backend-ram,size=1M,id=mb1" 406 " -device ivshmem-plain,memdev=mb1", false); 407 408 cleanup_vm(&state); 409} 410 411static void cleanup(void) 412{ 413 if (tmpshmem) { 414 munmap(tmpshmem, TMPSHMSIZE); 415 tmpshmem = NULL; 416 } 417 418 if (tmpshm) { 419 shm_unlink(tmpshm); 420 g_free(tmpshm); 421 tmpshm = NULL; 422 } 423 424 if (tmpserver) { 425 g_unlink(tmpserver); 426 g_free(tmpserver); 427 tmpserver = NULL; 428 } 429 430 if (tmpdir) { 431 g_rmdir(tmpdir); 432 tmpdir = NULL; 433 } 434} 435 436static void abrt_handler(void *data) 437{ 438 cleanup(); 439} 440 441static gchar *mktempshm(int size, int *fd) 442{ 443 while (true) { 444 gchar *name; 445 446 name = g_strdup_printf("/qtest-%u-%u", getpid(), g_test_rand_int()); 447 *fd = shm_open(name, O_CREAT|O_RDWR|O_EXCL, 448 S_IRWXU|S_IRWXG|S_IRWXO); 449 if (*fd > 0) { 450 g_assert(ftruncate(*fd, size) == 0); 451 return name; 452 } 453 454 g_free(name); 455 456 if (errno != EEXIST) { 457 perror("shm_open"); 458 return NULL; 459 } 460 } 461} 462 463int main(int argc, char **argv) 464{ 465 int ret, fd; 466 const char *arch = qtest_get_arch(); 467 gchar dir[] = "/tmp/ivshmem-test.XXXXXX"; 468 469 g_test_init(&argc, &argv, NULL); 470 471 qtest_add_abrt_handler(abrt_handler, NULL); 472 /* shm */ 473 tmpshm = mktempshm(TMPSHMSIZE, &fd); 474 if (!tmpshm) { 475 goto out; 476 } 477 tmpshmem = mmap(0, TMPSHMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 478 g_assert(tmpshmem != MAP_FAILED); 479 /* server */ 480 if (mkdtemp(dir) == NULL) { 481 g_error("mkdtemp: %s", g_strerror(errno)); 482 } 483 tmpdir = dir; 484 tmpserver = g_strconcat(tmpdir, "/server", NULL); 485 486 qtest_add_func("/ivshmem/single", test_ivshmem_single); 487 qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug); 488 qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev); 489 if (g_test_slow()) { 490 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 } 495 496out: 497 ret = g_test_run(); 498 cleanup(); 499 return ret; 500}