| /* |
| * The PCI Utilities -- Manipulate PCI Configuration Registers |
| * |
| * Copyright (c) 1998--2006 Martin Mares <mj@ucw.cz> |
| * |
| * Can be freely distributed and used under the terms of the GNU GPL. |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <unistd.h> |
| |
| #include "pciutils.h" |
| |
| static int force; /* Don't complain if no devices match */ |
| static int verbose; /* Verbosity level */ |
| static int demo_mode; /* Only show */ |
| |
| const char program_name[] = "setpci"; |
| |
| static struct pci_access *pacc; |
| |
| struct value { |
| unsigned int value; |
| unsigned int mask; |
| }; |
| |
| struct op { |
| struct op *next; |
| struct pci_dev **dev_vector; |
| unsigned int addr; |
| unsigned int width; /* Byte width of the access */ |
| int num_values; /* Number of values to write; <0=read */ |
| struct value values[0]; |
| }; |
| |
| static struct op *first_op, **last_op = &first_op; |
| static unsigned int max_values[] = { 0, 0xff, 0xffff, 0, 0xffffffff }; |
| |
| static struct pci_dev ** |
| select_devices(struct pci_filter *filt) |
| { |
| struct pci_dev *z, **a, **b; |
| int cnt = 1; |
| |
| for(z=pacc->devices; z; z=z->next) |
| if (pci_filter_match(filt, z)) |
| cnt++; |
| a = b = xmalloc(sizeof(struct device *) * cnt); |
| for(z=pacc->devices; z; z=z->next) |
| if (pci_filter_match(filt, z)) |
| *a++ = z; |
| *a = NULL; |
| return b; |
| } |
| |
| static void |
| exec_op(struct op *op, struct pci_dev *dev) |
| { |
| char *formats[] = { NULL, "%02x", "%04x", NULL, "%08x" }; |
| char *mask_formats[] = { NULL, "%02x->(%02x:%02x)->%02x", "%04x->(%04x:%04x)->%04x", NULL, "%08x->(%08x:%08x)->%08x" }; |
| unsigned int x, y; |
| int i, addr; |
| int width = op->width; |
| |
| if (verbose) |
| printf("%02x:%02x.%x:%02x", dev->bus, dev->dev, dev->func, op->addr); |
| addr = op->addr; |
| if (op->num_values >= 0) |
| { |
| for(i=0; i<op->num_values; i++) |
| { |
| if ((op->values[i].mask & max_values[width]) == max_values[width]) |
| { |
| x = op->values[i].value; |
| if (verbose) |
| { |
| putchar(' '); |
| printf(formats[width], op->values[i].value); |
| } |
| } |
| else |
| { |
| switch (width) |
| { |
| case 1: |
| y = pci_read_byte(dev, addr); |
| break; |
| case 2: |
| y = pci_read_word(dev, addr); |
| break; |
| default: |
| y = pci_read_long(dev, addr); |
| break; |
| } |
| x = (y & ~op->values[i].mask) | op->values[i].value; |
| if (verbose) |
| { |
| putchar(' '); |
| printf(mask_formats[width], y, op->values[i].value, op->values[i].mask, x); |
| } |
| } |
| if (!demo_mode) |
| { |
| switch (width) |
| { |
| case 1: |
| pci_write_byte(dev, addr, x); |
| break; |
| case 2: |
| pci_write_word(dev, addr, x); |
| break; |
| default: |
| pci_write_long(dev, addr, x); |
| break; |
| } |
| } |
| addr += width; |
| } |
| if (verbose) |
| putchar('\n'); |
| } |
| else |
| { |
| if (verbose) |
| printf(" = "); |
| switch (width) |
| { |
| case 1: |
| x = pci_read_byte(dev, addr); |
| break; |
| case 2: |
| x = pci_read_word(dev, addr); |
| break; |
| default: |
| x = pci_read_long(dev, addr); |
| break; |
| } |
| printf(formats[width], x); |
| putchar('\n'); |
| } |
| } |
| |
| static void |
| execute(struct op *op) |
| { |
| struct pci_dev **vec = NULL; |
| struct pci_dev **pdev, *dev; |
| struct op *oops; |
| |
| while (op) |
| { |
| pdev = vec = op->dev_vector; |
| while (dev = *pdev++) |
| for(oops=op; oops && oops->dev_vector == vec; oops=oops->next) |
| exec_op(oops, dev); |
| while (op && op->dev_vector == vec) |
| op = op->next; |
| } |
| } |
| |
| static void |
| scan_ops(struct op *op) |
| { |
| while (op) |
| { |
| if (op->num_values >= 0) |
| pacc->writeable = 1; |
| op = op->next; |
| } |
| } |
| |
| struct reg_name { |
| unsigned int offset; |
| unsigned int width; |
| const char *name; |
| }; |
| |
| static const struct reg_name pci_reg_names[] = { |
| { 0x00, 2, "VENDOR_ID", }, |
| { 0x02, 2, "DEVICE_ID", }, |
| { 0x04, 2, "COMMAND", }, |
| { 0x06, 2, "STATUS", }, |
| { 0x08, 1, "REVISION", }, |
| { 0x09, 1, "CLASS_PROG", }, |
| { 0x0a, 2, "CLASS_DEVICE", }, |
| { 0x0c, 1, "CACHE_LINE_SIZE", }, |
| { 0x0d, 1, "LATENCY_TIMER", }, |
| { 0x0e, 1, "HEADER_TYPE", }, |
| { 0x0f, 1, "BIST", }, |
| { 0x10, 4, "BASE_ADDRESS_0", }, |
| { 0x14, 4, "BASE_ADDRESS_1", }, |
| { 0x18, 4, "BASE_ADDRESS_2", }, |
| { 0x1c, 4, "BASE_ADDRESS_3", }, |
| { 0x20, 4, "BASE_ADDRESS_4", }, |
| { 0x24, 4, "BASE_ADDRESS_5", }, |
| { 0x28, 4, "CARDBUS_CIS", }, |
| { 0x2c, 4, "SUBSYSTEM_VENDOR_ID", }, |
| { 0x2e, 2, "SUBSYSTEM_ID", }, |
| { 0x30, 4, "ROM_ADDRESS", }, |
| { 0x3c, 1, "INTERRUPT_LINE", }, |
| { 0x3d, 1, "INTERRUPT_PIN", }, |
| { 0x3e, 1, "MIN_GNT", }, |
| { 0x3f, 1, "MAX_LAT", }, |
| { 0x18, 1, "PRIMARY_BUS", }, |
| { 0x19, 1, "SECONDARY_BUS", }, |
| { 0x1a, 1, "SUBORDINATE_BUS", }, |
| { 0x1b, 1, "SEC_LATENCY_TIMER", }, |
| { 0x1c, 1, "IO_BASE", }, |
| { 0x1d, 1, "IO_LIMIT", }, |
| { 0x1e, 2, "SEC_STATUS", }, |
| { 0x20, 2, "MEMORY_BASE", }, |
| { 0x22, 2, "MEMORY_LIMIT", }, |
| { 0x24, 2, "PREF_MEMORY_BASE", }, |
| { 0x26, 2, "PREF_MEMORY_LIMIT", }, |
| { 0x28, 4, "PREF_BASE_UPPER32", }, |
| { 0x2c, 4, "PREF_LIMIT_UPPER32", }, |
| { 0x30, 2, "IO_BASE_UPPER16", }, |
| { 0x32, 2, "IO_LIMIT_UPPER16", }, |
| { 0x38, 4, "BRIDGE_ROM_ADDRESS", }, |
| { 0x3e, 2, "BRIDGE_CONTROL", }, |
| { 0x10, 4, "CB_CARDBUS_BASE", }, |
| { 0x14, 2, "CB_CAPABILITIES", }, |
| { 0x16, 2, "CB_SEC_STATUS", }, |
| { 0x18, 1, "CB_BUS_NUMBER", }, |
| { 0x19, 1, "CB_CARDBUS_NUMBER", }, |
| { 0x1a, 1, "CB_SUBORDINATE_BUS", }, |
| { 0x1b, 1, "CB_CARDBUS_LATENCY", }, |
| { 0x1c, 4, "CB_MEMORY_BASE_0", }, |
| { 0x20, 4, "CB_MEMORY_LIMIT_0", }, |
| { 0x24, 4, "CB_MEMORY_BASE_1", }, |
| { 0x28, 4, "CB_MEMORY_LIMIT_1", }, |
| { 0x2c, 2, "CB_IO_BASE_0", }, |
| { 0x2e, 2, "CB_IO_BASE_0_HI", }, |
| { 0x30, 2, "CB_IO_LIMIT_0", }, |
| { 0x32, 2, "CB_IO_LIMIT_0_HI", }, |
| { 0x34, 2, "CB_IO_BASE_1", }, |
| { 0x36, 2, "CB_IO_BASE_1_HI", }, |
| { 0x38, 2, "CB_IO_LIMIT_1", }, |
| { 0x3a, 2, "CB_IO_LIMIT_1_HI", }, |
| { 0x40, 2, "CB_SUBSYSTEM_VENDOR_ID", }, |
| { 0x42, 2, "CB_SUBSYSTEM_ID", }, |
| { 0x44, 4, "CB_LEGACY_MODE_BASE", }, |
| { 0x00, 0, NULL } |
| }; |
| |
| static void NONRET |
| usage(char *msg, ...) |
| { |
| va_list args; |
| va_start(args, msg); |
| if (msg) |
| { |
| fprintf(stderr, "setpci: "); |
| vfprintf(stderr, msg, args); |
| fprintf(stderr, "\n\n"); |
| } |
| fprintf(stderr, |
| "Usage: setpci [<options>] (<device>+ <reg>[=<values>]*)*\n\ |
| -f\t\tDon't complain if there's nothing to do\n\ |
| -v\t\tBe verbose\n\ |
| -D\t\tList changes, don't commit them\n" |
| GENERIC_HELP |
| "<device>:\t-s [[[<domain>]:][<bus>]:][<slot>][.[<func>]]\n" |
| "\t|\t-d [<vendor>]:[<device>]\n" |
| "<reg>:\t\t<number>[.(B|W|L)]\n" |
| " |\t\t<name>\n" |
| "<values>:\t<value>[,<value>...]\n" |
| "<value>:\t<hex>\n" |
| " |\t<hex>:<mask>\n"); |
| exit(1); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| enum { STATE_INIT, STATE_GOT_FILTER, STATE_GOT_OP } state = STATE_INIT; |
| struct pci_filter filter; |
| struct pci_dev **selected_devices = NULL; |
| char *opts = GENERIC_OPTIONS ; |
| |
| if (argc == 2 && !strcmp(argv[1], "--version")) |
| { |
| puts("setpci version " PCIUTILS_VERSION); |
| return 0; |
| } |
| argc--; |
| argv++; |
| |
| pacc = pci_alloc(); |
| pacc->error = die; |
| |
| while (argc && argv[0][0] == '-') |
| { |
| char *c = argv[0]+1; |
| char *d = c; |
| char *e; |
| while (*c) |
| switch (*c) |
| { |
| case 'v': |
| verbose++; |
| c++; |
| break; |
| case 'f': |
| force++; |
| c++; |
| break; |
| case 'D': |
| demo_mode++; |
| c++; |
| break; |
| case 0: |
| break; |
| default: |
| if (e = strchr(opts, *c)) |
| { |
| char *arg; |
| c++; |
| if (e[1] == ':') |
| { |
| if (*c) |
| arg = c; |
| else if (argc > 1) |
| { |
| arg = argv[1]; |
| argc--; argv++; |
| } |
| else |
| usage(NULL); |
| c = ""; |
| } |
| else |
| arg = NULL; |
| if (!parse_generic_option(*e, pacc, arg)) |
| usage(NULL); |
| } |
| else |
| { |
| if (c != d) |
| usage(NULL); |
| goto next; |
| } |
| } |
| argc--; |
| argv++; |
| } |
| next: |
| |
| pci_init(pacc); |
| pci_scan_bus(pacc); |
| |
| while (argc) |
| { |
| char *c = argv[0]; |
| char *d, *e, *f; |
| int n, i; |
| struct op *op; |
| unsigned long ll; |
| unsigned int lim; |
| |
| if (*c == '-') |
| { |
| if (!c[1] || !strchr("sd", c[1])) |
| usage(NULL); |
| if (c[2]) |
| d = (c[2] == '=') ? c+3 : c+2; |
| else if (argc > 1) |
| { |
| argc--; |
| argv++; |
| d = argv[0]; |
| } |
| else |
| usage(NULL); |
| if (state != STATE_GOT_FILTER) |
| { |
| pci_filter_init(pacc, &filter); |
| state = STATE_GOT_FILTER; |
| } |
| switch (c[1]) |
| { |
| case 's': |
| if (d = pci_filter_parse_slot(&filter, d)) |
| die("-s: %s", d); |
| break; |
| case 'd': |
| if (d = pci_filter_parse_id(&filter, d)) |
| die("-d: %s", d); |
| break; |
| default: |
| usage(NULL); |
| } |
| } |
| else if (state == STATE_INIT) |
| usage(NULL); |
| else |
| { |
| if (state == STATE_GOT_FILTER) |
| selected_devices = select_devices(&filter); |
| if (!selected_devices[0] && !force) |
| fprintf(stderr, "setpci: Warning: No devices selected for `%s'.\n", c); |
| state = STATE_GOT_OP; |
| /* look for setting of values and count how many */ |
| d = strchr(c, '='); |
| if (d) |
| { |
| *d++ = 0; |
| if (!*d) |
| usage("Missing value"); |
| for(e=d, n=1; *e; e++) |
| if (*e == ',') |
| n++; |
| op = xmalloc(sizeof(struct op) + n*sizeof(struct value)); |
| } |
| else |
| { |
| n = -1; |
| op = xmalloc(sizeof(struct op)); |
| } |
| op->dev_vector = selected_devices; |
| op->num_values = n; |
| e = strchr(c, '.'); |
| if (e) |
| { |
| *e++ = 0; |
| if (e[1]) |
| usage("Missing width"); |
| switch (*e & 0xdf) |
| { |
| case 'B': |
| op->width = 1; break; |
| case 'W': |
| op->width = 2; break; |
| case 'L': |
| op->width = 4; break; |
| default: |
| usage("Invalid width \"%c\"", *e); |
| } |
| } |
| else |
| op->width = 1; |
| ll = strtol(c, &f, 16); |
| if (f && *f) |
| { |
| const struct reg_name *r; |
| for(r = pci_reg_names; r->name; r++) |
| if (!strcasecmp(r->name, c)) |
| break; |
| if (!r->name) |
| usage("Unknown register \"%s\"", c); |
| if (e && op->width != r->width) |
| usage("Explicit width doesn't correspond with the named register \"%s\"", c); |
| ll = r->offset; |
| op->width = r->width; |
| } |
| if (ll > 0x1000 || ll + op->width*((n < 0) ? 1 : n) > 0x1000) |
| die("Register number out of range!"); |
| if (ll & (op->width - 1)) |
| die("Unaligned register address!"); |
| op->addr = ll; |
| /* read in all the values to be set */ |
| for(i=0; i<n; i++) |
| { |
| e = strchr(d, ','); |
| if (e) |
| *e++ = 0; |
| ll = strtoul(d, &f, 16); |
| lim = max_values[op->width]; |
| if (f && *f && *f != ':') |
| usage("Invalid value \"%s\"", d); |
| if (ll > lim && ll < ~0UL - lim) |
| usage("Value \"%s\" is out of range", d); |
| op->values[i].value = ll; |
| if (f && *f == ':') |
| { |
| d = ++f; |
| ll = strtoul(d, &f, 16); |
| if (f && *f) |
| usage("Invalid mask \"%s\"", d); |
| if (ll > lim && ll < ~0UL - lim) |
| usage("Mask \"%s\" is out of range", d); |
| op->values[i].mask = ll; |
| op->values[i].value &= ll; |
| } |
| else |
| op->values[i].mask = ~0U; |
| d = e; |
| } |
| *last_op = op; |
| last_op = &op->next; |
| op->next = NULL; |
| } |
| argc--; |
| argv++; |
| } |
| if (state == STATE_INIT) |
| usage("No operation specified"); |
| |
| scan_ops(first_op); |
| execute(first_op); |
| |
| return 0; |
| } |