/* * Copyright © 2015 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "igt.h" #include "igt_gt.h" #include "intel_io.h" #include "intel_chipset.h" #include "intel_reg_spec.h" #ifdef HAVE_SYS_IO_H #include #else static inline int _not_supported(void) { fprintf(stderr, "portio-vga not supported\n"); exit(EXIT_FAILURE); } #define inb(port) _not_supported() #define outb(value, port) _not_supported() #define iopl(level) #endif /* HAVE_SYS_IO_H */ struct config { struct pci_device *pci_dev; char *mmiofile; uint32_t devid; /* read: number of registers to read */ uint32_t count; /* write: do a posting read */ bool post; /* decode register for all platforms */ bool all_platforms; /* spread out bits for convenience */ bool binary; /* register spec */ char *specfile; /* fd for engine access avoiding reopens */ int fd; struct reg *regs; ssize_t regcount; int verbosity; }; /* port desc must have been set */ static int set_reg_by_addr(struct config *config, struct reg *reg, uint32_t addr) { int i; reg->addr = addr; if (reg->name) free(reg->name); reg->name = NULL; for (i = 0; i < config->regcount; i++) { struct reg *r = &config->regs[i]; if (reg->port_desc.port != r->port_desc.port) continue; /* ->mmio_offset should be 0 for non-MMIO ports. */ if (addr + reg->mmio_offset == r->addr + r->mmio_offset) { /* Always output the "normalized" offset+addr. */ reg->mmio_offset = r->mmio_offset; reg->addr = r->addr; reg->name = r->name ? strdup(r->name) : NULL; break; } } return 0; } /* port desc must have been set */ static int set_reg_by_name(struct config *config, struct reg *reg, const char *name) { int i; reg->name = strdup(name); reg->addr = 0; for (i = 0; i < config->regcount; i++) { struct reg *r = &config->regs[i]; if (reg->port_desc.port != r->port_desc.port) continue; if (!r->name) continue; if (strcasecmp(name, r->name) == 0) { reg->addr = r->addr; /* Also get MMIO offset if not already specified. */ if (!reg->mmio_offset && r->mmio_offset) reg->mmio_offset = r->mmio_offset; return 0; } } return -1; } static void to_binary(char *buf, size_t buflen, uint32_t val) { int i; if (!buflen) return; *buf = '\0'; /* XXX: This quick and dirty implementation makes eyes hurt. */ for (i = 31; i >= 0; i--) { if (i % 8 == 0) snprintf(buf, buflen, " %2d", i); else snprintf(buf, buflen, " "); buflen -= strlen(buf); buf += strlen(buf); } snprintf(buf, buflen, "\n"); buflen -= strlen(buf); buf += strlen(buf); for (i = 31; i >= 0; i--) { snprintf(buf, buflen, " %s%d", i % 8 == 7 ? " " : "", !!(val & (1 << i))); buflen -= strlen(buf); buf += strlen(buf); } snprintf(buf, buflen, "\n"); } static void dump_decode(struct config *config, struct reg *reg, uint32_t val) { char decode[1300]; char tmp[1024]; char bin[200]; if (config->binary) to_binary(bin, sizeof(bin), val); else *bin = '\0'; intel_reg_spec_decode(tmp, sizeof(tmp), reg, val, config->all_platforms ? 0 : config->devid); if (*tmp) { /* We have a decode result, and maybe binary decode. */ if (config->all_platforms) snprintf(decode, sizeof(decode), "\n%s%s", tmp, bin); else snprintf(decode, sizeof(decode), " (%s)\n%s", tmp, bin); } else if (*bin) { /* No decode result, but binary decode. */ snprintf(decode, sizeof(decode), "\n%s", bin); } else { /* No decode nor binary decode. */ snprintf(decode, sizeof(decode), "\n"); } if (reg->port_desc.port == PORT_MMIO) { /* Omit port name for MMIO, optionally include MMIO offset. */ if (reg->mmio_offset) printf("%24s (0x%08x:0x%08x): 0x%08x%s", reg->name ?: "", reg->mmio_offset, reg->addr, val, decode); else printf("%35s (0x%08x): 0x%08x%s", reg->name ?: "", reg->addr, val, decode); } else { char name[100], addr[100]; /* If no name, use addr as name for easier copy pasting. */ if (reg->name) snprintf(name, sizeof(name), "%s:%s", reg->port_desc.name, reg->name); else snprintf(name, sizeof(name), "%s:0x%08x", reg->port_desc.name, reg->addr); /* Negative port numbers are not real sideband ports. */ if (reg->port_desc.port > PORT_NONE) snprintf(addr, sizeof(addr), "0x%02x:0x%08x", reg->port_desc.port, reg->addr); else snprintf(addr, sizeof(addr), "%s:0x%08x", reg->port_desc.name, reg->addr); printf("%24s (%s): 0x%08x%s", name, addr, val, decode); } } static const struct intel_execution_engine2 *find_engine(const char *name) { const struct intel_execution_engine2 *e; if (strlen(name) < 2) return NULL; if (name[0] == '-') name++; for (e = intel_execution_engines2; e->name; e++) { if (!strcasecmp(e->name, name)) return e; } return NULL; } static int register_srm(struct config *config, struct reg *reg, uint32_t *val_in) { const int gen = intel_gen(config->devid); const bool r64b = gen >= 8; const uint32_t ctx = 0; struct drm_i915_gem_exec_object2 obj[2]; struct drm_i915_gem_relocation_entry reloc[1]; struct drm_i915_gem_execbuffer2 execbuf; uint32_t *batch, *r; const struct intel_execution_engine2 *engine; bool secure; int fd, i; uint32_t val; if (config->fd == -1) { config->fd = __drm_open_driver(DRIVER_INTEL); if (config->fd == -1) { fprintf(stderr, "Error opening driver: %s", strerror(errno)); exit(EXIT_FAILURE); } } fd = config->fd; engine = find_engine(reg->engine); if (engine == NULL) exit(EXIT_FAILURE); secure = reg->engine[0] != '-'; memset(obj, 0, sizeof(obj)); obj[0].handle = gem_create(fd, 4096); obj[1].handle = gem_create(fd, 4096); obj[1].relocs_ptr = to_user_pointer(reloc); obj[1].relocation_count = 1; batch = gem_mmap__cpu(fd, obj[1].handle, 0, 4096, PROT_WRITE); gem_set_domain(fd, obj[1].handle, I915_GEM_DOMAIN_CPU, I915_GEM_DOMAIN_CPU); i = 0; if (val_in) { batch[i++] = MI_NOOP; batch[i++] = MI_NOOP; batch[i++] = MI_LOAD_REGISTER_IMM; batch[i++] = reg->addr; batch[i++] = *val_in; batch[i++] = MI_NOOP; } batch[i++] = 0x24 << 23 | (1 + r64b); /* SRM */ batch[i++] = reg->addr; reloc[0].target_handle = obj[0].handle; reloc[0].presumed_offset = obj[0].offset; reloc[0].offset = i * sizeof(uint32_t); reloc[0].delta = 0; reloc[0].read_domains = I915_GEM_DOMAIN_RENDER; reloc[0].write_domain = I915_GEM_DOMAIN_RENDER; batch[i++] = reloc[0].delta; if (r64b) batch[i++] = 0; batch[i++] = MI_BATCH_BUFFER_END; munmap(batch, 4096); memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 2; execbuf.flags = engine->flags; if (secure) execbuf.flags |= I915_EXEC_SECURE; if (config->verbosity > 0) printf("%s: using %sprivileged batch\n", engine->name, secure ? "" : "non-"); execbuf.rsvd1 = ctx; gem_execbuf(fd, &execbuf); gem_close(fd, obj[1].handle); r = gem_mmap__cpu(fd, obj[0].handle, 0, 4096, PROT_READ); gem_set_domain(fd, obj[0].handle, I915_GEM_DOMAIN_CPU, 0); val = r[0]; munmap(r, 4096); gem_close(fd, obj[0].handle); return val; } static int read_register(struct config *config, struct reg *reg, uint32_t *valp) { uint32_t val = 0; switch (reg->port_desc.port) { case PORT_MMIO: if (reg->engine) val = register_srm(config, reg, NULL); else val = INREG(reg->mmio_offset + reg->addr); break; case PORT_PORTIO_VGA: iopl(3); val = inb(reg->addr); iopl(0); break; case PORT_MMIO_VGA: val = INREG8(reg->addr); break; case PORT_BUNIT: case PORT_PUNIT: case PORT_NC: case PORT_DPIO: case PORT_GPIO_NC: case PORT_CCK: case PORT_CCU: case PORT_DPIO2: case PORT_FLISDSI: if (!IS_VALLEYVIEW(config->devid) && !IS_CHERRYVIEW(config->devid)) { fprintf(stderr, "port %s only supported on vlv/chv\n", reg->port_desc.name); return -1; } val = intel_iosf_sb_read(reg->port_desc.port, reg->addr); break; default: fprintf(stderr, "port %d not supported\n", reg->port_desc.port); return -1; } if (valp) *valp = val; return 0; } static void dump_register(struct config *config, struct reg *reg) { uint32_t val; if (read_register(config, reg, &val) == 0) dump_decode(config, reg, val); } static int write_register(struct config *config, struct reg *reg, uint32_t val) { int ret = 0; if (config->verbosity > 0) { printf("Before:\n"); dump_register(config, reg); } switch (reg->port_desc.port) { case PORT_MMIO: if (reg->engine) { register_srm(config, reg, &val); } else { OUTREG(reg->mmio_offset + reg->addr, val); } break; case PORT_PORTIO_VGA: if (val > 0xff) { fprintf(stderr, "value 0x%08x out of range for port %s\n", val, reg->port_desc.name); return -1; } iopl(3); outb(val, reg->addr); iopl(0); break; case PORT_MMIO_VGA: if (val > 0xff) { fprintf(stderr, "value 0x%08x out of range for port %s\n", val, reg->port_desc.name); return -1; } OUTREG8(reg->addr, val); break; case PORT_BUNIT: case PORT_PUNIT: case PORT_NC: case PORT_DPIO: case PORT_GPIO_NC: case PORT_CCK: case PORT_CCU: case PORT_DPIO2: case PORT_FLISDSI: if (!IS_VALLEYVIEW(config->devid) && !IS_CHERRYVIEW(config->devid)) { fprintf(stderr, "port %s only supported on vlv/chv\n", reg->port_desc.name); return -1; } intel_iosf_sb_write(reg->port_desc.port, reg->addr, val); break; default: fprintf(stderr, "port %d not supported\n", reg->port_desc.port); ret = -1; } if (config->verbosity > 0) { printf("After:\n"); dump_register(config, reg); } else if (config->post) { read_register(config, reg, NULL); } return ret; } static int parse_engine(struct reg *reg, const char *s) { const struct intel_execution_engine2 *e; e = find_engine(s); if (e) { reg->port_desc.port = PORT_MMIO; reg->port_desc.name = strdup(s); reg->port_desc.stride = 4; reg->engine = strdup(s); reg->mmio_offset = 0; } else { reg->engine = NULL; } return reg->engine == NULL; } /* s has [(PORTNAME|PORTNUM|ENGINE|MMIO-OFFSET):](REGNAME|REGADDR) */ static int parse_reg(struct config *config, struct reg *reg, const char *s) { unsigned long addr; char *endp; const char *p; int ret; memset(reg, 0, sizeof(*reg)); p = strchr(s, ':'); if (p == s) { ret = -1; } else if (p) { char *port_name = strndup(s, p - s); ret = parse_engine(reg, port_name); if (ret) ret = parse_port_desc(reg, port_name); free(port_name); p++; } else { /* * XXX: If port is not specified in input, see if the register * matches by name, and initialize port desc based on that. */ ret = parse_port_desc(reg, NULL); p = s; } if (ret) { fprintf(stderr, "invalid port in '%s'\n", s); return ret; } addr = strtoul(p, &endp, 16); if (endp > p && *endp == 0) { /* It's a number. */ ret = set_reg_by_addr(config, reg, addr); } else { /* Not a number, it's a name. */ ret = set_reg_by_name(config, reg, p); } return ret; } /* XXX: add support for register ranges, maybe REGISTER..REGISTER */ static int intel_reg_read(struct config *config, int argc, char *argv[]) { int i, j; if (argc == 1) { fprintf(stderr, "read: no registers specified\n"); return EXIT_FAILURE; } if (config->mmiofile) intel_mmio_use_dump_file(config->mmiofile); else intel_register_access_init(config->pci_dev, 0, -1); for (i = 1; i < argc; i++) { struct reg reg; if (parse_reg(config, ®, argv[i])) continue; for (j = 0; j < config->count; j++) { dump_register(config, ®); /* Update addr and name. */ set_reg_by_addr(config, ®, reg.addr + reg.port_desc.stride); } } intel_register_access_fini(); return EXIT_SUCCESS; } static int intel_reg_write(struct config *config, int argc, char *argv[]) { int i; if (argc == 1) { fprintf(stderr, "write: no registers specified\n"); return EXIT_FAILURE; } intel_register_access_init(config->pci_dev, 0, -1); for (i = 1; i < argc; i += 2) { struct reg reg; uint32_t val; char *endp; if (parse_reg(config, ®, argv[i])) continue; if (i + 1 == argc) { fprintf(stderr, "write: no value\n"); break; } val = strtoul(argv[i + 1], &endp, 16); if (endp == argv[i + 1] || *endp) { fprintf(stderr, "write: invalid value '%s'\n", argv[i + 1]); continue; } write_register(config, ®, val); } intel_register_access_fini(); return EXIT_SUCCESS; } static int intel_reg_dump(struct config *config, int argc, char *argv[]) { struct reg *reg; int i; if (config->mmiofile) intel_mmio_use_dump_file(config->mmiofile); else intel_register_access_init(config->pci_dev, 0, -1); for (i = 0; i < config->regcount; i++) { reg = &config->regs[i]; /* can't dump sideband with mmiofile */ if (config->mmiofile && reg->port_desc.port != PORT_MMIO) continue; dump_register(config, &config->regs[i]); } intel_register_access_fini(); return EXIT_SUCCESS; } static int intel_reg_snapshot(struct config *config, int argc, char *argv[]) { int mmio_bar = IS_GEN2(config->devid) ? 1 : 0; if (config->mmiofile) { fprintf(stderr, "specifying --mmio=FILE is not compatible\n"); return EXIT_FAILURE; } intel_mmio_use_pci_bar(config->pci_dev); /* XXX: error handling */ if (write(1, igt_global_mmio, config->pci_dev->regions[mmio_bar].size) == -1) fprintf(stderr, "Error writing snapshot: %s", strerror(errno)); if (config->verbosity > 0) printf("use this with --mmio=FILE --devid=0x%04X\n", config->devid); return EXIT_SUCCESS; } /* XXX: add support for reading and re-decoding a previously done dump */ static int intel_reg_decode(struct config *config, int argc, char *argv[]) { int i; if (argc == 1) { fprintf(stderr, "decode: no registers specified\n"); return EXIT_FAILURE; } for (i = 1; i < argc; i += 2) { struct reg reg; uint32_t val; char *endp; if (parse_reg(config, ®, argv[i])) continue; if (i + 1 == argc) { fprintf(stderr, "decode: no value\n"); break; } val = strtoul(argv[i + 1], &endp, 16); if (endp == argv[i + 1] || *endp) { fprintf(stderr, "decode: invalid value '%s'\n", argv[i + 1]); continue; } dump_decode(config, ®, val); } return EXIT_SUCCESS; } static int intel_reg_list(struct config *config, int argc, char *argv[]) { int i; for (i = 0; i < config->regcount; i++) { printf("%s\n", config->regs[i].name); } return EXIT_SUCCESS; } static int intel_reg_help(struct config *config, int argc, char *argv[]); struct command { const char *name; const char *description; const char *synopsis; int (*function)(struct config *config, int argc, char *argv[]); }; static const struct command commands[] = { { .name = "read", .function = intel_reg_read, .synopsis = "[--count=N] REGISTER [...]", .description = "read and decode specified register(s)", }, { .name = "write", .function = intel_reg_write, .synopsis = "[--post] REGISTER VALUE [REGISTER VALUE ...]", .description = "write value(s) to specified register(s)", }, { .name = "dump", .function = intel_reg_dump, .description = "dump all known registers", }, { .name = "decode", .function = intel_reg_decode, .synopsis = "REGISTER VALUE [REGISTER VALUE ...]", .description = "decode value(s) for specified register(s)", }, { .name = "snapshot", .function = intel_reg_snapshot, .description = "create a snapshot of the MMIO bar to stdout", }, { .name = "list", .function = intel_reg_list, .description = "list all known register names", }, { .name = "help", .function = intel_reg_help, .description = "show this help", }, }; static int intel_reg_help(struct config *config, int argc, char *argv[]) { const struct intel_execution_engine2 *e; int i; printf("Intel graphics register multitool\n\n"); printf("Usage: intel_reg [OPTION ...] COMMAND\n\n"); printf("COMMAND is one of:\n"); for (i = 0; i < ARRAY_SIZE(commands); i++) { printf(" %-14s%s\n", commands[i].name, commands[i].synopsis ?: ""); printf(" %-14s%s\n", "", commands[i].description); } printf("\n"); printf("REGISTER is defined as:\n"); printf(" [(PORTNAME|PORTNUM|ENGINE|MMIO-OFFSET):](REGNAME|REGADDR)\n"); printf("\n"); printf("PORTNAME is one of:\n"); intel_reg_spec_print_ports(); printf("\n\n"); printf("ENGINE is one of:\n"); for (e = intel_execution_engines2; e->name; e++) printf("%s -%s ", e->name, e->name); printf("\n\n"); printf("OPTIONS common to most COMMANDS:\n"); printf(" --spec=PATH Read register spec from directory or file\n"); printf(" --mmio=FILE Use an MMIO snapshot\n"); printf(" --devid=DEVID Specify PCI device ID for --mmio=FILE\n"); printf(" --all Decode registers for all known platforms\n"); printf(" --binary Binary dump registers\n"); printf(" --verbose Increase verbosity\n"); printf(" --quiet Reduce verbosity\n"); printf("\n"); printf("Environment variables:\n"); printf(" INTEL_REG_SPEC Read register spec from directory or file\n"); return EXIT_SUCCESS; } /* * Get codename for a gen5+ platform to be used for finding register spec file. */ static const char *get_codename(uint32_t devid) { return intel_get_device_info(devid)->codename; } /* * Get register definitions filename for devid in dir. Return 0 if found, * negative error code otherwise. */ static int get_reg_spec_file(char *buf, size_t buflen, const char *dir, uint32_t devid) { const char *codename; /* First, try file named after devid, e.g. "0412" for Haswell GT2. */ snprintf(buf, buflen, "%s/%04x", dir, devid); if (!access(buf, F_OK)) return 0; /* * Second, for gen5+, try file named after codename, e.g. "haswell" for * Haswell. */ codename = get_codename(devid); if (codename) { snprintf(buf, buflen, "%s/%s", dir, codename); if (!access(buf, F_OK)) return 0; } /* * Third, try file named after gen, e.g. "gen7" for Haswell (which is * technically 7.5 but this is how it works). */ snprintf(buf, buflen, "%s/gen%d", dir, intel_gen(devid)); if (!access(buf, F_OK)) return 0; return -ENOENT; } /* * Read register spec. */ static int read_reg_spec(struct config *config) { char buf[PATH_MAX]; const char *path; struct stat st; int r; path = config->specfile; if (!path) path = getenv("INTEL_REG_SPEC"); if (!path) path = IGT_DATADIR"/registers"; r = stat(path, &st); if (r) { fprintf(stderr, "Warning: stat '%s' failed: %s. " "Using builtin register spec.\n", path, strerror(errno)); goto builtin; } if (S_ISDIR(st.st_mode)) { r = get_reg_spec_file(buf, sizeof(buf), path, config->devid); if (r) { fprintf(stderr, "Warning: register spec not found in " "'%s'. Using builtin register spec.\n", path); goto builtin; } path = buf; } config->regcount = intel_reg_spec_file(&config->regs, path); if (config->regcount <= 0) { fprintf(stderr, "Warning: reading '%s' failed. " "Using builtin register spec.\n", path); goto builtin; } return config->regcount; builtin: /* Fallback to builtin register spec. */ config->regcount = intel_reg_spec_builtin(&config->regs, config->devid); return config->regcount; } enum opt { OPT_UNKNOWN = '?', OPT_END = -1, OPT_MMIO, OPT_DEVID, OPT_COUNT, OPT_POST, OPT_ALL, OPT_BINARY, OPT_SPEC, OPT_VERBOSE, OPT_QUIET, OPT_HELP, }; int main(int argc, char *argv[]) { int ret, i, index; char *endp; enum opt opt; const struct command *command = NULL; struct config config = { .count = 1, .fd = -1, }; bool help = false; static struct option options[] = { /* global options */ { "spec", required_argument, NULL, OPT_SPEC }, { "verbose", no_argument, NULL, OPT_VERBOSE }, { "quiet", no_argument, NULL, OPT_QUIET }, { "help", no_argument, NULL, OPT_HELP }, /* options specific to read and dump */ { "mmio", required_argument, NULL, OPT_MMIO }, { "devid", required_argument, NULL, OPT_DEVID }, /* options specific to read */ { "count", required_argument, NULL, OPT_COUNT }, /* options specific to write */ { "post", no_argument, NULL, OPT_POST }, /* options specific to read, dump and decode */ { "all", no_argument, NULL, OPT_ALL }, { "binary", no_argument, NULL, OPT_BINARY }, { 0 } }; for (opt = 0; opt != OPT_END; ) { opt = getopt_long(argc, argv, "", options, &index); switch (opt) { case OPT_MMIO: config.mmiofile = strdup(optarg); if (!config.mmiofile) { fprintf(stderr, "strdup: %s\n", strerror(errno)); return EXIT_FAILURE; } break; case OPT_DEVID: config.devid = strtoul(optarg, &endp, 16); if (*endp) { fprintf(stderr, "invalid devid '%s'\n", optarg); return EXIT_FAILURE; } break; case OPT_COUNT: config.count = strtol(optarg, &endp, 10); if (*endp) { fprintf(stderr, "invalid count '%s'\n", optarg); return EXIT_FAILURE; } break; case OPT_POST: config.post = true; break; case OPT_SPEC: config.specfile = strdup(optarg); if (!config.specfile) { fprintf(stderr, "strdup: %s\n", strerror(errno)); return EXIT_FAILURE; } break; case OPT_ALL: config.all_platforms = true; break; case OPT_BINARY: config.binary = true; break; case OPT_VERBOSE: config.verbosity++; break; case OPT_QUIET: config.verbosity--; break; case OPT_HELP: help = true; break; case OPT_END: break; case OPT_UNKNOWN: return EXIT_FAILURE; } } argc -= optind; argv += optind; if (help || (argc > 0 && strcmp(argv[0], "help") == 0)) return intel_reg_help(&config, argc, argv); if (argc == 0) { fprintf(stderr, "Command missing. Try intel_reg help.\n"); return EXIT_FAILURE; } if (config.mmiofile) { if (!config.devid) { fprintf(stderr, "--mmio requires --devid\n"); return EXIT_FAILURE; } } else { /* XXX: devid without --mmio could be useful for decode. */ if (config.devid) { fprintf(stderr, "--devid without --mmio\n"); return EXIT_FAILURE; } config.pci_dev = intel_get_pci_device(); config.devid = config.pci_dev->device_id; } if (read_reg_spec(&config) < 0) { return EXIT_FAILURE; } for (i = 0; i < ARRAY_SIZE(commands); i++) { if (strcmp(argv[0], commands[i].name) == 0) { command = &commands[i]; break; } } if (!command) { fprintf(stderr, "'%s' is not an intel-reg command\n", argv[0]); return EXIT_FAILURE; } ret = command->function(&config, argc, argv); free(config.mmiofile); if (config.fd >= 0) close(config.fd); return ret; }