1 /* $OpenBSD: ldomctl.c,v 1.38 2020/02/02 00:49:06 kn Exp $ */ 2 3 /* 4 * Copyright (c) 2012 Mark Kettenis 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/ioctl.h> 21 #include <sys/stat.h> 22 #include <err.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <stdlib.h> 26 #include <stdio.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <util.h> 30 31 #include "ds.h" 32 #include "hvctl.h" 33 #include "mdstore.h" 34 #include "mdesc.h" 35 #include "ldom_util.h" 36 #include "ldomctl.h" 37 38 extern struct ds_service pri_service; 39 40 struct command { 41 const char *cmd_name; 42 void (*cmd_func)(int, char **); 43 }; 44 45 __dead void usage(void); 46 47 struct guest_head guest_list; 48 49 uint64_t find_guest(const char *); 50 51 void fetch_pri(void); 52 53 void download(int argc, char **argv); 54 void dump(int argc, char **argv); 55 void list(int argc, char **argv); 56 void list_io(int argc, char **argv); 57 void xselect(int argc, char **argv); 58 void delete(int argc, char **argv); 59 void create_vdisk(int argc, char **argv); 60 void guest_start(int argc, char **argv); 61 void guest_stop(int argc, char **argv); 62 void guest_panic(int argc, char **argv); 63 void guest_status(int argc, char **argv); 64 void guest_console(int argc, char **argv); 65 void init_system(int argc, char **argv); 66 67 struct command commands[] = { 68 { "download", download }, 69 { "dump", dump }, 70 { "list", list }, 71 { "list-io", list_io }, 72 { "select", xselect }, 73 { "delete", delete }, 74 { "create-vdisk", create_vdisk }, 75 { "start", guest_start }, 76 { "stop", guest_stop }, 77 { "panic", guest_panic }, 78 { "status", guest_status }, 79 { "console", guest_console }, 80 { "init-system", init_system }, 81 { NULL, NULL } 82 }; 83 84 void hv_open(void); 85 void hv_close(void); 86 void hv_config(void); 87 void hv_read(uint64_t, void *, size_t); 88 void hv_write(uint64_t, void *, size_t); 89 90 int hvctl_seq = 1; 91 int hvctl_fd; 92 93 void *hvmd_buf; 94 size_t hvmd_len; 95 struct md *hvmd; 96 uint64_t hv_mdpa; 97 uint64_t hv_membase; 98 uint64_t hv_memsize; 99 100 extern void *pri_buf; 101 extern size_t pri_len; 102 103 int 104 main(int argc, char **argv) 105 { 106 struct command *cmdp; 107 108 if (argc < 2) 109 usage(); 110 111 /* Skip program name. */ 112 argv++; 113 argc--; 114 115 for (cmdp = commands; cmdp->cmd_name != NULL; cmdp++) 116 if (strcmp(argv[0], cmdp->cmd_name) == 0) 117 break; 118 if (cmdp->cmd_name == NULL) 119 usage(); 120 121 (cmdp->cmd_func)(argc, argv); 122 123 exit(EXIT_SUCCESS); 124 } 125 126 __dead void 127 usage(void) 128 { 129 fprintf(stderr, "usage:\t%1$s delete|select configuration\n" 130 "\t%1$s download directory\n" 131 "\t%1$s dump|list|list-io\n" 132 "\t%1$s init-system [-n] file\n" 133 "\t%1$s create-vdisk -s size file\n" 134 "\t%1$s panic|start [-c] domain\n" 135 "\t%1$s console|status|stop [domain]\n", 136 getprogname()); 137 138 exit(EXIT_FAILURE); 139 } 140 141 void 142 add_guest(struct md_node *node) 143 { 144 struct guest *guest; 145 struct md_prop *prop; 146 147 guest = xmalloc(sizeof(*guest)); 148 149 if (!md_get_prop_str(hvmd, node, "name", &guest->name)) 150 goto free; 151 if (!md_get_prop_val(hvmd, node, "gid", &guest->gid)) 152 goto free; 153 if (!md_get_prop_val(hvmd, node, "mdpa", &guest->mdpa)) 154 goto free; 155 156 guest->num_cpus = 0; 157 TAILQ_FOREACH(prop, &node->prop_list, link) { 158 if (prop->tag == MD_PROP_ARC && 159 strcmp(prop->name->str, "fwd") == 0) { 160 if (strcmp(prop->d.arc.node->name->str, "cpu") == 0) 161 guest->num_cpus++; 162 } 163 } 164 165 TAILQ_INSERT_TAIL(&guest_list, guest, link); 166 return; 167 168 free: 169 free(guest); 170 } 171 172 uint64_t 173 find_guest(const char *name) 174 { 175 struct guest *guest; 176 177 TAILQ_FOREACH(guest, &guest_list, link) { 178 if (strcmp(guest->name, name) == 0) 179 return guest->gid; 180 } 181 182 errx(EXIT_FAILURE, "unknown guest '%s'", name); 183 } 184 185 void 186 fetch_pri(void) 187 { 188 struct ds_conn *dc; 189 190 dc = ds_conn_open("/dev/spds", NULL); 191 ds_conn_register_service(dc, &pri_service); 192 while (pri_buf == NULL) 193 ds_conn_handle(dc); 194 } 195 196 void 197 dump(int argc, char **argv) 198 { 199 struct guest *guest; 200 struct md_header hdr; 201 void *md_buf; 202 size_t md_len; 203 char *name; 204 FILE *fp; 205 206 if (argc != 1) 207 usage(); 208 209 hv_config(); 210 211 fp = fopen("hv.md", "w"); 212 if (fp == NULL) 213 err(1, "fopen"); 214 fwrite(hvmd_buf, hvmd_len, 1, fp); 215 fclose(fp); 216 217 fetch_pri(); 218 219 fp = fopen("pri", "w"); 220 if (fp == NULL) 221 err(1, "fopen"); 222 fwrite(pri_buf, pri_len, 1, fp); 223 fclose(fp); 224 225 TAILQ_FOREACH(guest, &guest_list, link) { 226 hv_read(guest->mdpa, &hdr, sizeof(hdr)); 227 md_len = sizeof(hdr) + hdr.node_blk_sz + hdr.name_blk_sz + 228 hdr.data_blk_sz; 229 md_buf = xmalloc(md_len); 230 hv_read(guest->mdpa, md_buf, md_len); 231 232 if (asprintf(&name, "%s.md", guest->name) == -1) 233 err(1, "asprintf"); 234 235 fp = fopen(name, "w"); 236 if (fp == NULL) 237 err(1, "fopen"); 238 fwrite(md_buf, md_len, 1, fp); 239 fclose(fp); 240 241 free(name); 242 free(md_buf); 243 } 244 } 245 246 void 247 init_system(int argc, char **argv) 248 { 249 int ch, noaction = 0; 250 251 while ((ch = getopt(argc, argv, "n")) != -1) { 252 switch (ch) { 253 case 'n': 254 noaction = 1; 255 break; 256 default: 257 usage(); 258 } 259 } 260 argc -= optind; 261 argv += optind; 262 263 if (argc != 1) 264 usage(); 265 266 if (!noaction) 267 hv_config(); 268 269 build_config(argv[0], noaction); 270 } 271 272 void 273 list(int argc, char **argv) 274 { 275 struct ds_conn *dc; 276 struct mdstore_set *set; 277 278 if (argc != 1) 279 usage(); 280 281 hv_config(); 282 283 dc = ds_conn_open("/dev/spds", NULL); 284 mdstore_register(dc); 285 while (TAILQ_EMPTY(&mdstore_sets)) 286 ds_conn_handle(dc); 287 288 TAILQ_FOREACH(set, &mdstore_sets, link) { 289 printf("%s", set->name); 290 if (set->booted_set) 291 printf(" [current]"); 292 else if (set->boot_set) 293 printf(" [next]"); 294 printf("\n"); 295 } 296 } 297 298 void 299 list_io(int argc, char **argv) 300 { 301 if (argc != 1) 302 usage(); 303 304 list_components(); 305 } 306 307 void 308 xselect(int argc, char **argv) 309 { 310 struct ds_conn *dc; 311 312 if (argc != 2) 313 usage(); 314 315 hv_config(); 316 317 dc = ds_conn_open("/dev/spds", NULL); 318 mdstore_register(dc); 319 while (TAILQ_EMPTY(&mdstore_sets)) 320 ds_conn_handle(dc); 321 322 mdstore_select(dc, argv[1]); 323 } 324 325 void 326 delete(int argc, char **argv) 327 { 328 struct ds_conn *dc; 329 330 if (argc != 2) 331 usage(); 332 333 if (strcmp(argv[1], "factory-default") == 0) 334 errx(1, "\"%s\" should not be deleted", argv[1]); 335 336 hv_config(); 337 338 dc = ds_conn_open("/dev/spds", NULL); 339 mdstore_register(dc); 340 while (TAILQ_EMPTY(&mdstore_sets)) 341 ds_conn_handle(dc); 342 343 mdstore_delete(dc, argv[1]); 344 } 345 346 void 347 create_vdisk(int argc, char **argv) 348 { 349 int ch, fd, save_errno; 350 long long imgsize; 351 const char *imgfile_path; 352 353 while ((ch = getopt(argc, argv, "s:")) != -1) { 354 switch (ch) { 355 case 's': 356 if (scan_scaled(optarg, &imgsize) == -1) 357 err(1, "invalid size: %s", optarg); 358 break; 359 default: 360 usage(); 361 } 362 } 363 argc -= optind; 364 argv += optind; 365 366 if (argc != 1) 367 usage(); 368 369 imgfile_path = argv[0]; 370 371 /* Refuse to overwrite an existing image */ 372 if ((fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 373 S_IRUSR | S_IWUSR)) == -1) 374 err(1, "open"); 375 376 /* Extend to desired size */ 377 if (ftruncate(fd, (off_t)imgsize) == -1) { 378 save_errno = errno; 379 close(fd); 380 unlink(imgfile_path); 381 errno = save_errno; 382 err(1, "ftruncate"); 383 } 384 385 close(fd); 386 } 387 388 void 389 download(int argc, char **argv) 390 { 391 struct ds_conn *dc; 392 393 if (argc != 2) 394 usage(); 395 396 hv_config(); 397 398 dc = ds_conn_open("/dev/spds", NULL); 399 mdstore_register(dc); 400 while (TAILQ_EMPTY(&mdstore_sets)) 401 ds_conn_handle(dc); 402 403 mdstore_download(dc, argv[1]); 404 } 405 406 void 407 console_exec(uint64_t gid) 408 { 409 struct guest *guest; 410 char console_str[8]; 411 412 if (gid == 0) 413 errx(1, "no console for primary domain"); 414 415 TAILQ_FOREACH(guest, &guest_list, link) { 416 if (guest->gid != gid) 417 continue; 418 snprintf(console_str, sizeof(console_str), 419 "ttyV%llu", guest->gid - 1); 420 421 closefrom(STDERR_FILENO + 1); 422 execl(LDOMCTL_CU, LDOMCTL_CU, "-r", "-l", console_str, 423 (char *)NULL); 424 err(1, "failed to open console"); 425 } 426 } 427 428 void 429 guest_start(int argc, char **argv) 430 { 431 struct hvctl_msg msg; 432 ssize_t nbytes; 433 uint64_t gid; 434 int ch, console = 0; 435 436 while ((ch = getopt(argc, argv, "c")) != -1) { 437 switch (ch) { 438 case 'c': 439 console = 1; 440 break; 441 default: 442 usage(); 443 } 444 } 445 argc -= optind; 446 argv += optind; 447 448 if (argc != 1) 449 usage(); 450 451 hv_config(); 452 453 gid = find_guest(argv[0]); 454 455 /* 456 * Start guest domain. 457 */ 458 bzero(&msg, sizeof(msg)); 459 msg.hdr.op = HVCTL_OP_GUEST_START; 460 msg.hdr.seq = hvctl_seq++; 461 msg.msg.guestop.guestid = gid; 462 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 463 if (nbytes != sizeof(msg)) 464 err(1, "write"); 465 466 bzero(&msg, sizeof(msg)); 467 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 468 if (nbytes != sizeof(msg)) 469 err(1, "read"); 470 471 if (console) 472 console_exec(gid); 473 } 474 475 void 476 guest_stop(int argc, char **argv) 477 { 478 struct hvctl_msg msg; 479 ssize_t nbytes; 480 481 if (argc != 2) 482 usage(); 483 484 hv_config(); 485 486 /* 487 * Stop guest domain. 488 */ 489 bzero(&msg, sizeof(msg)); 490 msg.hdr.op = HVCTL_OP_GUEST_STOP; 491 msg.hdr.seq = hvctl_seq++; 492 msg.msg.guestop.guestid = find_guest(argv[1]); 493 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 494 if (nbytes != sizeof(msg)) 495 err(1, "write"); 496 497 bzero(&msg, sizeof(msg)); 498 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 499 if (nbytes != sizeof(msg)) 500 err(1, "read"); 501 } 502 503 void 504 guest_panic(int argc, char **argv) 505 { 506 struct hvctl_msg msg; 507 ssize_t nbytes; 508 uint64_t gid; 509 int ch, console = 0; 510 511 while ((ch = getopt(argc, argv, "c")) != -1) { 512 switch (ch) { 513 case 'c': 514 console = 1; 515 break; 516 default: 517 usage(); 518 } 519 } 520 argc -= optind; 521 argv += optind; 522 523 if (argc != 1) 524 usage(); 525 526 hv_config(); 527 528 gid = find_guest(argv[0]); 529 530 /* 531 * Stop guest domain. 532 */ 533 bzero(&msg, sizeof(msg)); 534 msg.hdr.op = HVCTL_OP_GUEST_PANIC; 535 msg.hdr.seq = hvctl_seq++; 536 msg.msg.guestop.guestid = gid; 537 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 538 if (nbytes != sizeof(msg)) 539 err(1, "write"); 540 541 bzero(&msg, sizeof(msg)); 542 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 543 if (nbytes != sizeof(msg)) 544 err(1, "read"); 545 546 if (console) 547 console_exec(gid); 548 } 549 550 void 551 guest_status(int argc, char **argv) 552 { 553 struct hvctl_msg msg; 554 ssize_t nbytes; 555 struct hvctl_rs_guest_state state; 556 struct hvctl_rs_guest_softstate softstate; 557 struct hvctl_rs_guest_util util; 558 struct guest *guest; 559 uint64_t gid = -1; 560 uint64_t total_cycles, yielded_cycles; 561 double utilisation = 0.0; 562 const char *state_str; 563 char buf[32]; 564 char console_str[8] = "-"; 565 566 if (argc < 1 || argc > 2) 567 usage(); 568 569 hv_config(); 570 571 if (argc == 2) 572 gid = find_guest(argv[1]); 573 574 TAILQ_FOREACH(guest, &guest_list, link) { 575 if (gid != -1 && guest->gid != gid) 576 continue; 577 578 /* 579 * Request status. 580 */ 581 bzero(&msg, sizeof(msg)); 582 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 583 msg.hdr.seq = hvctl_seq++; 584 msg.msg.resstat.res = HVCTL_RES_GUEST; 585 msg.msg.resstat.resid = guest->gid; 586 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_STATE; 587 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 588 if (nbytes != sizeof(msg)) 589 err(1, "write"); 590 591 bzero(&msg, sizeof(msg)); 592 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 593 if (nbytes != sizeof(msg)) 594 err(1, "read"); 595 596 memcpy(&state, msg.msg.resstat.data, sizeof(state)); 597 switch (state.state) { 598 case GUEST_STATE_STOPPED: 599 state_str = "stopped"; 600 break; 601 case GUEST_STATE_RESETTING: 602 state_str = "resetting"; 603 break; 604 case GUEST_STATE_NORMAL: 605 state_str = "running"; 606 607 bzero(&msg, sizeof(msg)); 608 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 609 msg.hdr.seq = hvctl_seq++; 610 msg.msg.resstat.res = HVCTL_RES_GUEST; 611 msg.msg.resstat.resid = guest->gid; 612 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_SOFT_STATE; 613 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 614 if (nbytes != sizeof(msg)) 615 err(1, "write"); 616 617 bzero(&msg, sizeof(msg)); 618 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 619 if (nbytes != sizeof(msg)) 620 err(1, "read"); 621 622 memcpy(&softstate, msg.msg.resstat.data, 623 sizeof(softstate)); 624 625 bzero(&msg, sizeof(msg)); 626 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 627 msg.hdr.seq = hvctl_seq++; 628 msg.msg.resstat.res = HVCTL_RES_GUEST; 629 msg.msg.resstat.resid = guest->gid; 630 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_UTILISATION; 631 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 632 if (nbytes != sizeof(msg)) 633 err(1, "write"); 634 635 bzero(&msg, sizeof(msg)); 636 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 637 if (nbytes != sizeof(msg)) 638 err(1, "read"); 639 640 memcpy(&util, msg.msg.resstat.data, sizeof(util)); 641 642 total_cycles = util.active_delta * guest->num_cpus 643 - util.stopped_cycles; 644 yielded_cycles = util.yielded_cycles; 645 if (yielded_cycles <= total_cycles) 646 utilisation = (100.0 * (total_cycles 647 - yielded_cycles)) / total_cycles; 648 else 649 utilisation = 0.0; 650 651 break; 652 case GUEST_STATE_SUSPENDED: 653 state_str = "suspended"; 654 break; 655 case GUEST_STATE_EXITING: 656 state_str = "exiting"; 657 break; 658 case GUEST_STATE_UNCONFIGURED: 659 state_str = "unconfigured"; 660 break; 661 default: 662 snprintf(buf, sizeof(buf), "unknown (%lld)", 663 state.state); 664 state_str = buf; 665 break; 666 } 667 668 /* primary has no console */ 669 if (guest->gid != 0) { 670 snprintf(console_str, sizeof(console_str), 671 "ttyV%llu", guest->gid - 1); 672 } 673 674 printf("%-16s %-8s %-16s %-32s %3.0f%%\n", guest->name, 675 console_str, state_str, state.state == GUEST_STATE_NORMAL ? 676 softstate.soft_state_str : "-", utilisation); 677 } 678 } 679 680 void 681 guest_console(int argc, char **argv) 682 { 683 uint64_t gid; 684 685 if (argc != 2) 686 usage(); 687 688 hv_config(); 689 690 gid = find_guest(argv[1]); 691 692 console_exec(gid); 693 } 694 695 void 696 hv_open(void) 697 { 698 struct hvctl_msg msg; 699 ssize_t nbytes; 700 uint64_t code; 701 702 hvctl_fd = open("/dev/hvctl", O_RDWR, 0); 703 if (hvctl_fd == -1) 704 err(1, "cannot open /dev/hvctl"); 705 706 /* 707 * Say "Hello". 708 */ 709 bzero(&msg, sizeof(msg)); 710 msg.hdr.op = HVCTL_OP_HELLO; 711 msg.hdr.seq = hvctl_seq++; 712 msg.msg.hello.major = 1; 713 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 714 if (nbytes != sizeof(msg)) 715 err(1, "write"); 716 717 bzero(&msg, sizeof(msg)); 718 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 719 if (nbytes != sizeof(msg)) 720 err(1, "read"); 721 722 code = msg.msg.clnge.code ^ 0xbadbeef20; 723 724 /* 725 * Respond to challenge. 726 */ 727 bzero(&msg, sizeof(msg)); 728 msg.hdr.op = HVCTL_OP_RESPONSE; 729 msg.hdr.seq = hvctl_seq++; 730 msg.msg.clnge.code = code ^ 0x12cafe42a; 731 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 732 if (nbytes != sizeof(msg)) 733 err(1, "write"); 734 735 bzero(&msg, sizeof(msg)); 736 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 737 if (nbytes != sizeof(msg)) 738 err(1, "read"); 739 } 740 741 void 742 hv_close(void) 743 { 744 close(hvctl_fd); 745 hvctl_fd = -1; 746 } 747 748 void 749 hv_config(void) 750 { 751 struct hvctl_msg msg; 752 ssize_t nbytes; 753 struct md_header hdr; 754 struct md_node *node; 755 struct md_prop *prop; 756 757 hv_open(); 758 759 /* 760 * Request config. 761 */ 762 bzero(&msg, sizeof(msg)); 763 msg.hdr.op = HVCTL_OP_GET_HVCONFIG; 764 msg.hdr.seq = hvctl_seq++; 765 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 766 if (nbytes != sizeof(msg)) 767 err(1, "write"); 768 769 bzero(&msg, sizeof(msg)); 770 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 771 if (nbytes != sizeof(msg)) 772 err(1, "read"); 773 774 hv_membase = msg.msg.hvcnf.hv_membase; 775 hv_memsize = msg.msg.hvcnf.hv_memsize; 776 777 hv_mdpa = msg.msg.hvcnf.hvmdp; 778 hv_read(hv_mdpa, &hdr, sizeof(hdr)); 779 hvmd_len = sizeof(hdr) + hdr.node_blk_sz + hdr.name_blk_sz + 780 hdr.data_blk_sz; 781 hvmd_buf = xmalloc(hvmd_len); 782 hv_read(hv_mdpa, hvmd_buf, hvmd_len); 783 784 hvmd = md_ingest(hvmd_buf, hvmd_len); 785 node = md_find_node(hvmd, "guests"); 786 TAILQ_INIT(&guest_list); 787 TAILQ_FOREACH(prop, &node->prop_list, link) { 788 if (prop->tag == MD_PROP_ARC && 789 strcmp(prop->name->str, "fwd") == 0) 790 add_guest(prop->d.arc.node); 791 } 792 } 793 794 void 795 hv_read(uint64_t addr, void *buf, size_t len) 796 { 797 struct hv_io hi; 798 799 hi.hi_cookie = addr; 800 hi.hi_addr = buf; 801 hi.hi_len = len; 802 803 if (ioctl(hvctl_fd, HVIOCREAD, &hi) == -1) 804 err(1, "ioctl"); 805 } 806 807 void 808 hv_write(uint64_t addr, void *buf, size_t len) 809 { 810 struct hv_io hi; 811 812 hi.hi_cookie = addr; 813 hi.hi_addr = buf; 814 hi.hi_len = len; 815 816 if (ioctl(hvctl_fd, HVIOCWRITE, &hi) == -1) 817 err(1, "ioctl"); 818 } 819