You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1056 lines
23 KiB
1056 lines
23 KiB
/*
|
|
* 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 = 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;
|
|
}
|