1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (C) 2018 Universita` di Pisa 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/types.h> 31 #include <sys/stat.h> 32 #include <sys/ioctl.h> 33 #include <sys/mman.h> 34 #include <ctype.h> 35 #include <fcntl.h> 36 #include <inttypes.h> 37 #include <stdlib.h> 38 #include <stdio.h> 39 #include <stdarg.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <errno.h> 43 44 //#define NMREQ_DEBUG 45 #ifdef NMREQ_DEBUG 46 #define NETMAP_WITH_LIBS 47 #define ED(...) D(__VA_ARGS__) 48 #else 49 #define ED(...) 50 /* an identifier is a possibly empty sequence of alphanum characters and 51 * underscores 52 */ 53 static int 54 nm_is_identifier(const char *s, const char *e) 55 { 56 for (; s != e; s++) { 57 if (!isalnum(*s) && *s != '_') { 58 return 0; 59 } 60 } 61 62 return 1; 63 } 64 #endif /* NMREQ_DEBUG */ 65 66 #include <net/netmap_user.h> 67 #define LIBNETMAP_NOTHREADSAFE 68 #include "libnetmap.h" 69 70 void 71 nmreq_push_option(struct nmreq_header *h, struct nmreq_option *o) 72 { 73 o->nro_next = h->nr_options; 74 h->nr_options = (uintptr_t)o; 75 } 76 77 struct nmreq_prefix { 78 const char *prefix; /* the constant part of the prefix */ 79 size_t len; /* its strlen() */ 80 uint32_t flags; 81 #define NR_P_ID (1U << 0) /* whether an identifier is needed */ 82 #define NR_P_SKIP (1U << 1) /* whether the scope must be passed to netmap */ 83 #define NR_P_EMPTYID (1U << 2) /* whether an empty identifier is allowed */ 84 }; 85 86 #define declprefix(prefix, flags) { (prefix), (sizeof(prefix) - 1), (flags) } 87 88 static struct nmreq_prefix nmreq_prefixes[] = { 89 declprefix("netmap", NR_P_SKIP), 90 declprefix(NM_BDG_NAME, NR_P_ID|NR_P_EMPTYID), 91 { NULL } /* terminate the list */ 92 }; 93 94 void 95 nmreq_header_init(struct nmreq_header *h, uint16_t reqtype, void *body) 96 { 97 memset(h, 0, sizeof(*h)); 98 h->nr_version = NETMAP_API; 99 h->nr_reqtype = reqtype; 100 h->nr_body = (uintptr_t)body; 101 } 102 103 int 104 nmreq_header_decode(const char **pifname, struct nmreq_header *h, struct nmctx *ctx) 105 { 106 const char *scan = NULL; 107 const char *vpname = NULL; 108 const char *pipesep = NULL; 109 u_int namelen; 110 const char *ifname = *pifname; 111 struct nmreq_prefix *p; 112 113 scan = ifname; 114 for (p = nmreq_prefixes; p->prefix != NULL; p++) { 115 if (!strncmp(scan, p->prefix, p->len)) 116 break; 117 } 118 if (p->prefix == NULL) { 119 nmctx_ferror(ctx, "%s: invalid request, prefix unknown or missing", *pifname); 120 goto fail; 121 } 122 scan += p->len; 123 124 vpname = index(scan, ':'); 125 if (vpname == NULL) { 126 nmctx_ferror(ctx, "%s: missing ':'", ifname); 127 goto fail; 128 } 129 if (vpname != scan) { 130 /* there is an identifier, can we accept it? */ 131 if (!(p->flags & NR_P_ID)) { 132 nmctx_ferror(ctx, "%s: no identifier allowed between '%s' and ':'", *pifname, p->prefix); 133 goto fail; 134 } 135 136 if (!nm_is_identifier(scan, vpname)) { 137 nmctx_ferror(ctx, "%s: invalid identifier '%.*s'", *pifname, vpname - scan, scan); 138 goto fail; 139 } 140 } else { 141 if ((p->flags & NR_P_ID) && !(p->flags & NR_P_EMPTYID)) { 142 nmctx_ferror(ctx, "%s: identifier is missing between '%s' and ':'", *pifname, p->prefix); 143 goto fail; 144 } 145 } 146 ++vpname; /* skip the colon */ 147 if (p->flags & NR_P_SKIP) 148 ifname = vpname; 149 scan = vpname; 150 151 /* scan for a separator */ 152 for (; *scan && !index("-*^/@", *scan); scan++) 153 ; 154 155 /* search for possible pipe indicators */ 156 for (pipesep = vpname; pipesep != scan && !index("{}", *pipesep); pipesep++) 157 ; 158 159 if (pipesep != scan) { 160 pipesep++; 161 if (*pipesep == '\0') { 162 nmctx_ferror(ctx, "%s: invalid empty pipe name", *pifname); 163 goto fail; 164 } 165 if (!nm_is_identifier(pipesep, scan)) { 166 nmctx_ferror(ctx, "%s: invalid pipe name '%.*s'", *pifname, scan - pipesep, pipesep); 167 goto fail; 168 } 169 } 170 171 namelen = scan - ifname; 172 if (namelen >= sizeof(h->nr_name)) { 173 nmctx_ferror(ctx, "name '%.*s' too long", namelen, ifname); 174 goto fail; 175 } 176 if (namelen == 0) { 177 nmctx_ferror(ctx, "%s: invalid empty port name", *pifname); 178 goto fail; 179 } 180 181 /* fill the header */ 182 memcpy(h->nr_name, ifname, namelen); 183 h->nr_name[namelen] = '\0'; 184 ED("name %s", h->nr_name); 185 186 *pifname = scan; 187 188 return 0; 189 fail: 190 errno = EINVAL; 191 return -1; 192 } 193 194 195 /* 196 * 0 not recognized 197 * -1 error 198 * >= 0 mem_id 199 */ 200 int32_t 201 nmreq_get_mem_id(const char **pifname, struct nmctx *ctx) 202 { 203 int fd = -1; 204 struct nmreq_header gh; 205 struct nmreq_port_info_get gb; 206 const char *ifname; 207 208 errno = 0; 209 ifname = *pifname; 210 211 if (ifname == NULL) 212 goto fail; 213 214 /* try to look for a netmap port with this name */ 215 fd = open("/dev/netmap", O_RDWR); 216 if (fd < 0) { 217 nmctx_ferror(ctx, "cannot open /dev/netmap: %s", strerror(errno)); 218 goto fail; 219 } 220 nmreq_header_init(&gh, NETMAP_REQ_PORT_INFO_GET, &gb); 221 if (nmreq_header_decode(&ifname, &gh, ctx) < 0) { 222 goto fail; 223 } 224 memset(&gb, 0, sizeof(gb)); 225 if (ioctl(fd, NIOCCTRL, &gh) < 0) { 226 nmctx_ferror(ctx, "cannot get info for '%s': %s", *pifname, strerror(errno)); 227 goto fail; 228 } 229 *pifname = ifname; 230 close(fd); 231 return gb.nr_mem_id; 232 233 fail: 234 if (fd >= 0) 235 close(fd); 236 if (!errno) 237 errno = EINVAL; 238 return -1; 239 } 240 241 242 int 243 nmreq_register_decode(const char **pifname, struct nmreq_register *r, struct nmctx *ctx) 244 { 245 enum { P_START, P_RNGSFXOK, P_GETNUM, P_FLAGS, P_FLAGSOK, P_MEMID, P_ONESW } p_state; 246 long num; 247 const char *scan = *pifname; 248 uint32_t nr_mode; 249 uint16_t nr_mem_id; 250 uint16_t nr_ringid; 251 uint64_t nr_flags; 252 253 errno = 0; 254 255 /* fill the request */ 256 257 p_state = P_START; 258 /* defaults */ 259 nr_mode = NR_REG_ALL_NIC; /* default for no suffix */ 260 nr_mem_id = r->nr_mem_id; /* if non-zero, further updates are disabled */ 261 nr_ringid = 0; 262 nr_flags = 0; 263 while (*scan) { 264 switch (p_state) { 265 case P_START: 266 switch (*scan) { 267 case '^': /* only SW ring */ 268 nr_mode = NR_REG_SW; 269 p_state = P_ONESW; 270 break; 271 case '*': /* NIC and SW */ 272 nr_mode = NR_REG_NIC_SW; 273 p_state = P_RNGSFXOK; 274 break; 275 case '-': /* one NIC ring pair */ 276 nr_mode = NR_REG_ONE_NIC; 277 p_state = P_GETNUM; 278 break; 279 case '/': /* start of flags */ 280 p_state = P_FLAGS; 281 break; 282 case '@': /* start of memid */ 283 p_state = P_MEMID; 284 break; 285 default: 286 nmctx_ferror(ctx, "unknown modifier: '%c'", *scan); 287 goto fail; 288 } 289 scan++; 290 break; 291 case P_RNGSFXOK: 292 switch (*scan) { 293 case '/': 294 p_state = P_FLAGS; 295 break; 296 case '@': 297 p_state = P_MEMID; 298 break; 299 default: 300 nmctx_ferror(ctx, "unexpected character: '%c'", *scan); 301 goto fail; 302 } 303 scan++; 304 break; 305 case P_GETNUM: 306 if (!isdigit(*scan)) { 307 nmctx_ferror(ctx, "got '%s' while expecting a number", scan); 308 goto fail; 309 } 310 num = strtol(scan, (char **)&scan, 10); 311 if (num < 0 || num >= NETMAP_RING_MASK) { 312 nmctx_ferror(ctx, "'%ld' out of range [0, %d)", 313 num, NETMAP_RING_MASK); 314 goto fail; 315 } 316 nr_ringid = num & NETMAP_RING_MASK; 317 p_state = P_RNGSFXOK; 318 break; 319 case P_FLAGS: 320 case P_FLAGSOK: 321 switch (*scan) { 322 case '@': 323 p_state = P_MEMID; 324 scan++; 325 continue; 326 case 'x': 327 nr_flags |= NR_EXCLUSIVE; 328 break; 329 case 'z': 330 nr_flags |= NR_ZCOPY_MON; 331 break; 332 case 't': 333 nr_flags |= NR_MONITOR_TX; 334 break; 335 case 'r': 336 nr_flags |= NR_MONITOR_RX; 337 break; 338 case 'R': 339 nr_flags |= NR_RX_RINGS_ONLY; 340 break; 341 case 'T': 342 nr_flags |= NR_TX_RINGS_ONLY; 343 break; 344 default: 345 nmctx_ferror(ctx, "unrecognized flag: '%c'", *scan); 346 goto fail; 347 } 348 scan++; 349 p_state = P_FLAGSOK; 350 break; 351 case P_MEMID: 352 if (!isdigit(*scan)) { 353 scan--; /* escape to options */ 354 goto out; 355 } 356 num = strtol(scan, (char **)&scan, 10); 357 if (num <= 0) { 358 nmctx_ferror(ctx, "invalid mem_id: '%ld'", num); 359 goto fail; 360 } 361 if (nr_mem_id && nr_mem_id != num) { 362 nmctx_ferror(ctx, "invalid setting of mem_id to %ld (already set to %"PRIu16")", num, nr_mem_id); 363 goto fail; 364 } 365 nr_mem_id = num; 366 p_state = P_RNGSFXOK; 367 break; 368 case P_ONESW: 369 if (!isdigit(*scan)) { 370 p_state = P_RNGSFXOK; 371 } else { 372 nr_mode = NR_REG_ONE_SW; 373 p_state = P_GETNUM; 374 } 375 break; 376 } 377 } 378 if (p_state == P_MEMID && !*scan) { 379 nmctx_ferror(ctx, "invalid empty mem_id"); 380 goto fail; 381 } 382 if (p_state != P_START && p_state != P_RNGSFXOK && 383 p_state != P_FLAGSOK && p_state != P_MEMID && p_state != P_ONESW) { 384 nmctx_ferror(ctx, "unexpected end of request"); 385 goto fail; 386 } 387 out: 388 ED("flags: %s %s %s %s %s %s", 389 (nr_flags & NR_EXCLUSIVE) ? "EXCLUSIVE" : "", 390 (nr_flags & NR_ZCOPY_MON) ? "ZCOPY_MON" : "", 391 (nr_flags & NR_MONITOR_TX) ? "MONITOR_TX" : "", 392 (nr_flags & NR_MONITOR_RX) ? "MONITOR_RX" : "", 393 (nr_flags & NR_RX_RINGS_ONLY) ? "RX_RINGS_ONLY" : "", 394 (nr_flags & NR_TX_RINGS_ONLY) ? "TX_RINGS_ONLY" : ""); 395 r->nr_mode = nr_mode; 396 r->nr_ringid = nr_ringid; 397 r->nr_flags = nr_flags; 398 r->nr_mem_id = nr_mem_id; 399 *pifname = scan; 400 return 0; 401 402 fail: 403 if (!errno) 404 errno = EINVAL; 405 return -1; 406 } 407 408 409 static int 410 nmreq_option_parsekeys(const char *prefix, char *body, struct nmreq_opt_parser *p, 411 struct nmreq_parse_ctx *pctx) 412 { 413 char *scan; 414 char delim1; 415 struct nmreq_opt_key *k; 416 417 scan = body; 418 delim1 = *scan; 419 while (delim1 != '\0') { 420 char *key, *value; 421 char delim; 422 size_t vlen; 423 424 key = scan; 425 for ( scan++; *scan != '\0' && *scan != '=' && *scan != ','; scan++) { 426 if (*scan == '-') 427 *scan = '_'; 428 } 429 delim = *scan; 430 *scan = '\0'; 431 scan++; 432 for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL; 433 k++) { 434 if (!strcmp(k->key, key)) 435 goto found; 436 437 } 438 nmctx_ferror(pctx->ctx, "unknown key: '%s'", key); 439 errno = EINVAL; 440 return -1; 441 found: 442 if (pctx->keys[k->id] != NULL) { 443 nmctx_ferror(pctx->ctx, "option '%s': duplicate key '%s', already set to '%s'", 444 prefix, key, pctx->keys[k->id]); 445 errno = EINVAL; 446 return -1; 447 } 448 value = scan; 449 for ( ; *scan != '\0' && *scan != ','; scan++) 450 ; 451 delim1 = *scan; 452 *scan = '\0'; 453 vlen = scan - value; 454 scan++; 455 if (delim == '=') { 456 pctx->keys[k->id] = (vlen ? value : NULL); 457 } else { 458 if (!(k->flags & NMREQ_OPTK_ALLOWEMPTY)) { 459 nmctx_ferror(pctx->ctx, "option '%s': missing '=value' for key '%s'", 460 prefix, key); 461 errno = EINVAL; 462 return -1; 463 } 464 pctx->keys[k->id] = key; 465 } 466 } 467 /* now check that all no-default keys have been assigned */ 468 for (k = p->keys; (k - p->keys) < NMREQ_OPT_MAXKEYS && k->key != NULL; k++) { 469 if ((k->flags & NMREQ_OPTK_MUSTSET) && pctx->keys[k->id] == NULL) { 470 nmctx_ferror(pctx->ctx, "option '%s': mandatory key '%s' not assigned", 471 prefix, k->key); 472 errno = EINVAL; 473 return -1; 474 } 475 } 476 return 0; 477 } 478 479 480 static int 481 nmreq_option_decode1(char *opt, struct nmreq_opt_parser *parsers, 482 void *token, struct nmctx *ctx) 483 { 484 struct nmreq_opt_parser *p; 485 const char *prefix; 486 char *scan; 487 char delim; 488 struct nmreq_parse_ctx pctx; 489 int i; 490 491 prefix = opt; 492 /* find the delimiter */ 493 for (scan = opt; *scan != '\0' && *scan != ':' && *scan != '='; scan++) 494 ; 495 delim = *scan; 496 *scan = '\0'; 497 scan++; 498 /* find the prefix */ 499 for (p = parsers; p != NULL; p = p->next) { 500 if (!strcmp(prefix, p->prefix)) 501 break; 502 } 503 if (p == NULL) { 504 nmctx_ferror(ctx, "unknown option: '%s'", prefix); 505 errno = EINVAL; 506 return -1; 507 } 508 if (p->flags & NMREQ_OPTF_DISABLED) { 509 nmctx_ferror(ctx, "option '%s' is not supported", prefix); 510 errno = EOPNOTSUPP; 511 return -1; 512 } 513 /* prepare the parse context */ 514 pctx.ctx = ctx; 515 pctx.token = token; 516 for (i = 0; i < NMREQ_OPT_MAXKEYS; i++) 517 pctx.keys[i] = NULL; 518 switch (delim) { 519 case '\0': 520 /* no body */ 521 if (!(p->flags & NMREQ_OPTF_ALLOWEMPTY)) { 522 nmctx_ferror(ctx, "syntax error: missing body after '%s'", 523 prefix); 524 errno = EINVAL; 525 return -1; 526 } 527 break; 528 case '=': /* the body goes to the default option key, if any */ 529 if (p->default_key < 0 || p->default_key >= NMREQ_OPT_MAXKEYS) { 530 nmctx_ferror(ctx, "syntax error: '=' not valid after '%s'", 531 prefix); 532 errno = EINVAL; 533 return -1; 534 } 535 if (*scan == '\0') { 536 nmctx_ferror(ctx, "missing value for option '%s'", prefix); 537 errno = EINVAL; 538 return -1; 539 } 540 pctx.keys[p->default_key] = scan; 541 break; 542 case ':': /* parse 'key=value' strings */ 543 if (nmreq_option_parsekeys(prefix, scan, p, &pctx) < 0) 544 return -1; 545 break; 546 } 547 return p->parse(&pctx); 548 } 549 550 int 551 nmreq_options_decode(const char *opt, struct nmreq_opt_parser parsers[], 552 void *token, struct nmctx *ctx) 553 { 554 const char *scan, *opt1; 555 char *w; 556 size_t len; 557 int ret; 558 559 if (*opt == '\0') 560 return 0; /* empty list, OK */ 561 562 if (*opt != '@') { 563 nmctx_ferror(ctx, "option list does not start with '@'"); 564 errno = EINVAL; 565 return -1; 566 } 567 568 scan = opt; 569 do { 570 scan++; /* skip the plus */ 571 opt1 = scan; /* start of option */ 572 /* find the end of the option */ 573 for ( ; *scan != '\0' && *scan != '@'; scan++) 574 ; 575 len = scan - opt1; 576 if (len == 0) { 577 nmctx_ferror(ctx, "invalid empty option"); 578 errno = EINVAL; 579 return -1; 580 } 581 w = nmctx_malloc(ctx, len + 1); 582 if (w == NULL) { 583 nmctx_ferror(ctx, "out of memory"); 584 errno = ENOMEM; 585 return -1; 586 } 587 memcpy(w, opt1, len); 588 w[len] = '\0'; 589 ret = nmreq_option_decode1(w, parsers, token, ctx); 590 nmctx_free(ctx, w); 591 if (ret < 0) 592 return -1; 593 } while (*scan != '\0'); 594 595 return 0; 596 } 597 598 struct nmreq_option * 599 nmreq_find_option(struct nmreq_header *h, uint32_t t) 600 { 601 struct nmreq_option *o = NULL; 602 603 nmreq_foreach_option(h, o) { 604 if (o->nro_reqtype == t) 605 break; 606 } 607 return o; 608 } 609 610 void 611 nmreq_remove_option(struct nmreq_header *h, struct nmreq_option *o) 612 { 613 struct nmreq_option **nmo; 614 615 for (nmo = (struct nmreq_option **)&h->nr_options; *nmo != NULL; 616 nmo = (struct nmreq_option **)&(*nmo)->nro_next) { 617 if (*nmo == o) { 618 *((uint64_t *)(*nmo)) = o->nro_next; 619 o->nro_next = (uint64_t)(uintptr_t)NULL; 620 break; 621 } 622 } 623 } 624 625 void 626 nmreq_free_options(struct nmreq_header *h) 627 { 628 struct nmreq_option *o, *next; 629 630 /* 631 * Note: can't use nmreq_foreach_option() here; it frees the 632 * list as it's walking and nmreq_foreach_option() isn't 633 * modification-safe. 634 */ 635 for (o = (struct nmreq_option *)(uintptr_t)h->nr_options; o != NULL; 636 o = next) { 637 next = (struct nmreq_option *)(uintptr_t)o->nro_next; 638 free(o); 639 } 640 } 641 642 const char* 643 nmreq_option_name(uint32_t nro_reqtype) 644 { 645 switch (nro_reqtype) { 646 case NETMAP_REQ_OPT_EXTMEM: 647 return "extmem"; 648 case NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS: 649 return "sync-kloop-eventfds"; 650 case NETMAP_REQ_OPT_CSB: 651 return "csb"; 652 case NETMAP_REQ_OPT_SYNC_KLOOP_MODE: 653 return "sync-kloop-mode"; 654 case NETMAP_REQ_OPT_OFFSETS: 655 return "offsets"; 656 default: 657 return "unknown"; 658 } 659 } 660 661 #if 0 662 #include <inttypes.h> 663 static void 664 nmreq_dump(struct nmport_d *d) 665 { 666 printf("header:\n"); 667 printf(" nr_version: %"PRIu16"\n", d->hdr.nr_version); 668 printf(" nr_reqtype: %"PRIu16"\n", d->hdr.nr_reqtype); 669 printf(" nr_reserved: %"PRIu32"\n", d->hdr.nr_reserved); 670 printf(" nr_name: %s\n", d->hdr.nr_name); 671 printf(" nr_options: %lx\n", (unsigned long)d->hdr.nr_options); 672 printf(" nr_body: %lx\n", (unsigned long)d->hdr.nr_body); 673 printf("\n"); 674 printf("register (%p):\n", (void *)d->hdr.nr_body); 675 printf(" nr_mem_id: %"PRIu16"\n", d->reg.nr_mem_id); 676 printf(" nr_ringid: %"PRIu16"\n", d->reg.nr_ringid); 677 printf(" nr_mode: %lx\n", (unsigned long)d->reg.nr_mode); 678 printf(" nr_flags: %lx\n", (unsigned long)d->reg.nr_flags); 679 printf("\n"); 680 if (d->hdr.nr_options) { 681 struct nmreq_opt_extmem *e = (struct nmreq_opt_extmem *)d->hdr.nr_options; 682 printf("opt_extmem (%p):\n", e); 683 printf(" nro_opt.nro_next: %lx\n", (unsigned long)e->nro_opt.nro_next); 684 printf(" nro_opt.nro_reqtype: %"PRIu32"\n", e->nro_opt.nro_reqtype); 685 printf(" nro_usrptr: %lx\n", (unsigned long)e->nro_usrptr); 686 printf(" nro_info.nr_memsize %"PRIu64"\n", e->nro_info.nr_memsize); 687 } 688 printf("\n"); 689 printf("mem (%p):\n", d->mem); 690 printf(" refcount: %d\n", d->mem->refcount); 691 printf(" mem: %p\n", d->mem->mem); 692 printf(" size: %zu\n", d->mem->size); 693 printf("\n"); 694 printf("rings:\n"); 695 printf(" tx: [%d, %d]\n", d->first_tx_ring, d->last_tx_ring); 696 printf(" rx: [%d, %d]\n", d->first_rx_ring, d->last_rx_ring); 697 } 698 int 699 main(int argc, char *argv[]) 700 { 701 struct nmport_d *d; 702 703 if (argc < 2) { 704 fprintf(stderr, "usage: %s netmap-expr\n", argv[0]); 705 return 1; 706 } 707 708 d = nmport_open(argv[1]); 709 if (d != NULL) { 710 nmreq_dump(d); 711 nmport_close(d); 712 } 713 714 return 0; 715 } 716 #endif 717