| /* |
| * 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 <errno.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #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 <sys/io.h> |
| #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 = gem_class_instance_to_eb_flags(fd, |
| engine->class, |
| engine->instance); |
| 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; |
| } |