# CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # PROG= pcitool include $(SRC)/cmd/Makefile.cmd # Hammerhead: amd64-only SUBDIRS= $(MACH64) all : TARGET = all install : TARGET = install clean : TARGET = clean clobber : TARGET = clobber lint : TARGET = lint .KEEP_STATE: all: $(SUBDIRS) clean clobber lint: $(SUBDIRS) install: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: include $(SRC)/cmd/Makefile.targ # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # PROG = pcitool OBJS = pcitool.o pcitool_ui.o pcitool_usage.o SRCS = $(OBJS:%.o=../%.c) include $(SRC)/cmd/Makefile.cmd # Hammerhead: Use $(SRC)/uts instead of relative UTSBASE which breaks from amd64/ UTSBASE = $(SRC)/uts LDLIBS += -ldevinfo # Hammerhead: Add i86pc include path for sys/apic_ctlr.h # Use CPPFLAGS (not CFLAGS) because Makefile.master.64 resets CFLAGS CPPFLAGS += -D$(MACH) -I$(UTSBASE)/common -I$(UTSBASE)/i86pc CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-variable LINTFLAGS += -I$(UTSBASE)/common .KEEP_STATE: all: $(PROG) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) install: all $(PROG) clean: $(RM) $(OBJS) $(PROG) lint: $(LINT.c) $(SRCS) $(LDLIBS) %.o: ../%.c $(COMPILE.c) -o $@ $< include $(SRC)/cmd/Makefile.targ # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # Copyright 2025 Hammerhead Project # include ../Makefile.com include $(SRC)/cmd/Makefile.cmd.64 install: all $(ROOTPROG64) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* This file is the main module for the pcitool. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __x86 #include #endif #include #include #include "pcitool_ui.h" /* First 16 longs of device PCI config header. */ typedef union { uint8_t bytes[16 * sizeof (uint32_t)]; uint32_t dwords[16]; } pci_conf_hdr_t; /* Used by probe printing functions. */ typedef struct { uint16_t cfg_offset; /* Offset of data within config space. */ uint8_t size; /* Size of desired data field. */ char *abbrev_hdr; /* Abbreviated header for this data. */ char *full_hdr; /* Full header for this data, verbose option. */ } field_type_t; /* Used to package many args into one arg for probe di_node walk function. */ typedef struct { pcitool_uiargs_t *input_args_p; char *pathname; di_prom_handle_t di_phdl; } probe_walk_args_t; /* * Read config space in native processor endianness. Endian-neutral * processing can then take place. On big endian machines, MSB and LSB * of little endian data end up switched if read as little endian. * They are in correct order if read as big endian. */ #if defined(__sparc) #define NATIVE_ENDIAN PCITOOL_ACC_ATTR_ENDN_BIG #elif defined(__x86) #define NATIVE_ENDIAN PCITOOL_ACC_ATTR_ENDN_LTL #else #error "ISA is neither __sparc nor __x86" #endif /* status error lookup table. */ static struct { pcitool_errno_t value; char *string; } pcitool_stat_str[] = { { PCITOOL_SUCCESS, "No error status returned from driver" }, { PCITOOL_INVALID_CPUID, "CPU is non-existent or not online" }, { PCITOOL_INVALID_INO, "INO is out of range or invalid" }, { PCITOOL_INVALID_MSI, "MSI is out of range or invalid" }, { PCITOOL_PENDING_INTRTIMEOUT, "Timeout waiting for pending interrupts to clear" }, { PCITOOL_REGPROP_NOTWELLFORMED, "Reg property has invalid format" }, { PCITOOL_INVALID_ADDRESS, "Address out of range or invalid" }, { PCITOOL_NOT_ALIGNED, "Improper address alignment for access attempted" }, { PCITOOL_OUT_OF_RANGE, "Argument out of range" }, { PCITOOL_END_OF_RANGE, "End of address range" }, { PCITOOL_ROM_DISABLED, "Device ROM is disabled. Cannot read" }, { PCITOOL_ROM_WRITE, "Write to ROM not allowed" }, { PCITOOL_IO_ERROR, "IO error encountered" }, { PCITOOL_INVALID_SIZE, "Size is invalid for this platform" }, { 0, NULL } }; /* Used with ^C handler to stop looping in repeat mode in do_device_or_nexus. */ static boolean_t keep_looping = B_TRUE; static void signal_handler(int dummy); static char *strstatus(pcitool_errno_t pcitool_status); static int open_node(char *device, pcitool_uiargs_t *input_args_p); static void print_probe_value(pci_conf_hdr_t *config_hdr_p, uint16_t offset, uint8_t size); static void print_probe_info_verbose(pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p); static void print_probe_info_nonverbose(pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p); static void print_probe_info(pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p, boolean_t verbose); static int get_config_header(int fd, uint8_t bus_no, uint8_t dev_no, uint8_t func_no, pci_conf_hdr_t *config_hdr_p); static int supports_ari(int fd, uint8_t bus_no); static int probe_dev(int fd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p); static int do_probe(int fd, di_node_t di_node, di_prom_handle_t di_phdl, pcitool_uiargs_t *input_args_p); static int process_nexus_node(di_node_t node, di_minor_t minor, void *arg); static int do_probe_walk(pcitool_uiargs_t *input_args_p, char *pathname); static void print_bytedump_header(boolean_t do_chardump); static int bytedump_get(int fd, int cmd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p); static uint32_t set_acc_attr(pcitool_uiargs_t *input_args_p); static int do_single_access(int fd, int cmd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p); static int do_device_or_nexus(int fd, pcitool_uiargs_t *input_args_p); static void print_intr_info(pcitool_intr_get_t *iget_p); static int get_single_interrupt(int fd, pcitool_intr_get_t **iget_pp, pcitool_uiargs_t *input_args_p); static int get_interrupts(int fd, pcitool_uiargs_t *input_args_p); static int set_interrupts(int fd, pcitool_uiargs_t *input_args_p); static int do_interrupts(int fd, pcitool_uiargs_t *input_args_p); /* *************** General ************** */ /* * Handler for ^C to stop looping. */ /*ARGSUSED*/ static void signal_handler(int dummy) { keep_looping = B_FALSE; } /* * Print string based on PCItool status returned from driver. */ static char * strstatus(pcitool_errno_t pcitool_status) { int i; for (i = 0; pcitool_stat_str[i].string != NULL; i++) { if (pcitool_stat_str[i].value == pcitool_status) { return (pcitool_stat_str[i].string); } } return ("Unknown status returned from driver."); } static int open_node(char *device, pcitool_uiargs_t *input_args_p) { int fd; char *path; /* For building full nexus pathname. */ int stringsize; /* Device name size. */ char *prefix; char *suffix; char *format; static char slash_devices[] = {"/devices"}; static char wcolon[] = {"%s%s:%s"}; static char wocolon[] = {"%s%s%s"}; /* Check for names starting with /devices. */ prefix = (strstr(device, slash_devices) == device) ? "" : slash_devices; format = wcolon; if (input_args_p->flags & INTR_FLAG) { if (strstr(device, PCI_MINOR_INTR) == device + (strlen(device) - strlen(PCI_MINOR_INTR))) { suffix = ""; format = wocolon; } else { suffix = PCI_MINOR_INTR; } } else { if (strstr(device, PCI_MINOR_REG) == device + (strlen(device) - strlen(PCI_MINOR_REG))) { suffix = ""; format = wocolon; } else { suffix = PCI_MINOR_REG; } } /* * Build nexus pathname. * User specified /pci@1f,700000 becomes /devices/pci@1f,700000:intr * for interrupt nodes, and ...:reg for register nodes. * * ...The 2 at the end leaves room for a : and the terminating NULL. */ stringsize = strlen(prefix) + strlen(device) + strlen(suffix) + 2; path = malloc(stringsize); /*LINTED*/ (void) snprintf(path, stringsize, format, prefix, device, suffix); /* Open the nexus. */ if ((fd = open(path, O_RDWR)) == -1) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Could not open nexus node %s: %s\n", path, strerror(errno)); } } return (fd); } /* ****************** Probe **************** */ /* The following are used by the probe printing functions. */ /* Header 0 and 1 config space headers have these fields. */ static field_type_t first_fields[] = { { PCI_CONF_VENID, 2, "Vend", "Vendor ID" }, { PCI_CONF_DEVID, 2, "Dev ", "Device ID" }, { PCI_CONF_COMM, 2, "Cmd ", "Command" }, { PCI_CONF_STAT, 2, "Stat", "Status" }, { PCI_CONF_REVID, 1, "Rv", "Revision ID" }, { PCI_CONF_PROGCLASS, 3, "Class ", "Class Code" }, { PCI_CONF_CACHE_LINESZ, 1, "Ca", "Cache Line Size" }, { PCI_CONF_LATENCY_TIMER, 1, "LT", "Latency Timer" }, { PCI_CONF_HEADER, 1, "Hd", "Header Type" }, { PCI_CONF_BIST, 1, "BI", "BIST" }, { 0, 0, NULL, NULL } }; /* Header 0 (for regular devices) have these fields. */ static field_type_t last_dev_fields[] = { { PCI_CONF_BASE0, 4, "BAR0", "Base Address Register 0 (@10)" }, { PCI_CONF_BASE1, 4, "BAR1", "Base Address Register 1 (@14)" }, { PCI_CONF_BASE2, 4, "BAR2", "Base Address Register 2 (@18)" }, { PCI_CONF_BASE3, 4, "BAR3", "Base Address Register 3 (@1C)" }, { PCI_CONF_BASE4, 4, "BAR4", "Base Address Register 4 (@20)" }, { PCI_CONF_BASE5, 4, "BAR5", "Base Address Register 5 (@24)" }, { PCI_CONF_ROM, 4, "ROM", "Expansion ROM Base Address Register (@30)" }, { 0, 0, NULL, NULL } }; /* Header 1 (PCI-PCI bridge devices) have these fields. */ static field_type_t last_pcibrg_fields[] = { { PCI_CONF_BASE0, 4, "BAR0", "Base Address Register 0 (@10)" }, { PCI_CONF_BASE1, 4, "BAR1", "Base Address Register 1 (@14)" }, { PCI_BCNF_ROM, 4, "ROM", "Expansion ROM Base Address Register (@38)" }, { 0, 0, NULL, NULL } }; /* Header 2 (PCI-Cardbus bridge devices) have these fields. */ static field_type_t last_cbbrg_fields[] = { { PCI_CBUS_SOCK_REG, 4, "SCKT", "Socket/ExCA Base Address (@10)" }, { 0, 0, NULL, NULL } }; #define FMT_SIZE 7 static void print_probe_value(pci_conf_hdr_t *config_hdr_p, uint16_t offset, uint8_t size) { char format[FMT_SIZE]; /* Size cannot be any larger than 4 bytes. This is not checked. */ uint32_t value = 0; /* Build format of print, "%.x" */ (void) snprintf(format, FMT_SIZE, "%%%d.%dx ", size * 2, size * 2); while (size-- > 0) { value = (value << 8) + config_hdr_p->bytes[offset + size]; } /*LINTED*/ (void) printf(format, value); } static void print_probe_info_verbose(pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p) { field_type_t *last_fields = NULL; int i; (void) printf("\n" "Bus Number: %x Device Number: %x Function Number: %x\n", info_p->bus_no, info_p->dev_no, info_p->func_no); if (info_p->phys_addr != 0) { (void) printf("Physical Address: 0x%" PRIx64 " \n", info_p->phys_addr); } switch (config_hdr_p->bytes[PCI_CONF_HEADER] & PCI_HEADER_TYPE_M) { case PCI_HEADER_ZERO: /* Header type 0 is a regular device. */ last_fields = last_dev_fields; break; case PCI_HEADER_PPB: /* Header type 1 is a PCI-PCI bridge. */ last_fields = last_pcibrg_fields; (void) printf("PCI-PCI bridge\n"); break; case PCI_HEADER_CARDBUS: /* Header type 2 is a cardbus bridge */ last_fields = last_cbbrg_fields; (void) printf("PCI-Cardbus bridge\n"); break; default: (void) printf("Unknown device\n"); break; } if (last_fields != NULL) { for (i = 0; first_fields[i].size != 0; i++) { (void) printf("%s: ", first_fields[i].full_hdr); print_probe_value(config_hdr_p, first_fields[i].cfg_offset, first_fields[i].size); (void) putchar('\n'); } for (i = 0; last_fields[i].size != 0; i++) { (void) printf("%s: ", last_fields[i].full_hdr); print_probe_value(config_hdr_p, last_fields[i].cfg_offset, last_fields[i].size); (void) putchar('\n'); } } } static void print_probe_info_nonverbose(pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p) { int i; (void) printf("%2.2x %2.2x %1.1x ", info_p->bus_no, info_p->dev_no, info_p->func_no); for (i = 0; first_fields[i].size != 0; i++) { print_probe_value(config_hdr_p, first_fields[i].cfg_offset, first_fields[i].size); } (void) putchar('\n'); } /* * Print device information retrieved during probe mode. * Takes the PCI config header, plus address information retrieved from the * driver. * * When called with config_hdr_p == NULL, this function just prints a header * when not in verbose mode. */ static void print_probe_info( pci_conf_hdr_t *config_hdr_p, pcitool_reg_t *info_p, boolean_t verbose) { int i; /* Print header if not in verbose mode. */ if (config_hdr_p == NULL) { if (!verbose) { /* Bus dev func not from tble */ (void) printf("B D F "); for (i = 0; first_fields[i].size != 0; i++) { (void) printf("%s ", first_fields[i].abbrev_hdr); } (void) putchar('\n'); } return; } if (verbose) { print_probe_info_verbose(config_hdr_p, info_p); } else { print_probe_info_nonverbose(config_hdr_p, info_p); } } /* * Retrieve first 16 dwords of device's config header, except for the first * dword. First 16 dwords are defined by the PCI specification. */ static int get_config_header(int fd, uint8_t bus_no, uint8_t dev_no, uint8_t func_no, pci_conf_hdr_t *config_hdr_p) { pcitool_reg_t cfg_prg; int i; int rval = SUCCESS; /* Prepare a local pcitool_reg_t so as to not disturb the caller's. */ cfg_prg.offset = 0; cfg_prg.acc_attr = PCITOOL_ACC_ATTR_SIZE_4 + NATIVE_ENDIAN; cfg_prg.bus_no = bus_no; cfg_prg.dev_no = dev_no; cfg_prg.func_no = func_no; cfg_prg.barnum = 0; cfg_prg.user_version = PCITOOL_VERSION; /* Get dwords 1-15 of config space. They must be read as uint32_t. */ for (i = 1; i < (sizeof (pci_conf_hdr_t) / sizeof (uint32_t)); i++) { cfg_prg.offset += sizeof (uint32_t); if ((rval = ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg)) != SUCCESS) { break; } config_hdr_p->dwords[i] = (uint32_t)cfg_prg.data; } return (rval); } static int supports_ari(int fd, uint8_t bus_no) { pcitool_reg_t cfg_prg; int deadcount = 0; uint32_t data, hdr_next_ptr, hdr_cap_id; uint8_t dev_no = 0; uint8_t func_no = 0; /* Prepare a local pcitool_reg_t so as to not disturb the caller's. */ cfg_prg.bus_no = bus_no; cfg_prg.dev_no = dev_no; cfg_prg.func_no = func_no; cfg_prg.barnum = 0; cfg_prg.user_version = PCITOOL_VERSION; cfg_prg.offset = 0; cfg_prg.acc_attr = PCITOOL_ACC_ATTR_SIZE_4 + PCITOOL_ACC_ATTR_ENDN_LTL; if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg) != SUCCESS) { return (FAILURE); } data = (uint32_t)cfg_prg.data; if (data == (uint32_t)(-1)) return (FAILURE); cfg_prg.offset = PCI_CONF_COMM; if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg) != SUCCESS) { return (FAILURE); } data = (uint32_t)cfg_prg.data; if (!((data >> 16) & PCI_STAT_CAP)) return (FAILURE); cfg_prg.offset = PCI_CONF_CAP_PTR; if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg) != SUCCESS) { return (FAILURE); } data = (uint32_t)cfg_prg.data; hdr_next_ptr = data & 0xff; hdr_cap_id = 0; /* * Find the PCIe capability. */ while ((hdr_next_ptr != PCI_CAP_NEXT_PTR_NULL) && (hdr_cap_id != PCI_CAP_ID_PCI_E)) { if (hdr_next_ptr < 0x40) break; cfg_prg.offset = hdr_next_ptr; if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg) != SUCCESS) return (FAILURE); data = (uint32_t)cfg_prg.data; hdr_next_ptr = (data >> 8) & 0xFF; hdr_cap_id = data & 0xFF; if (deadcount++ > 100) return (FAILURE); } if (hdr_cap_id != PCI_CAP_ID_PCI_E) return (FAILURE); /* Found a PCIe Capability */ hdr_next_ptr = 0x100; hdr_cap_id = 0; /* * Now find the ARI Capability. */ while ((hdr_next_ptr != PCI_CAP_NEXT_PTR_NULL) && (hdr_cap_id != 0xe)) { if (hdr_next_ptr < 0x40) break; cfg_prg.offset = hdr_next_ptr; if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &cfg_prg) != SUCCESS) { return (FAILURE); } data = (uint32_t)cfg_prg.data; hdr_next_ptr = (data >> 20) & 0xFFF; hdr_cap_id = data & 0xFFFF; if (deadcount++ > 100) return (FAILURE); } if (hdr_cap_id != 0xe) return (FAILURE); return (SUCCESS); } /* * Identify problematic southbridges. These have device id 0x5249 and * vendor id 0x10b9. Check for revision ID 0 and class code 060400 as well. * Values are little endian, so they are reversed for SPARC. * * Check for these southbridges on all architectures, as the issue is a * southbridge issue, independent of processor. * * If one of these is found during probing, skip probing other devs/funcs on * the rest of the bus, since the southbridge and all devs underneath will * otherwise disappear. */ #if (NATIVE_ENDIAN == PCITOOL_ACC_ATTR_ENDN_BIG) #define U45_SB_DEVID_VID 0xb9104952 #define U45_SB_CLASS_RID 0x00000406 #else #define U45_SB_DEVID_VID 0x524910b9 #define U45_SB_CLASS_RID 0x06040000 #endif /* * Probe device's functions. Modifies many fields in the prg_p. */ static int probe_dev(int fd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p) { pci_conf_hdr_t config_hdr; boolean_t multi_function_device = B_FALSE; int func; int first_func = 0; int last_func = PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT; int rval = SUCCESS; if (input_args_p->flags & FUNC_SPEC_FLAG) { first_func = last_func = input_args_p->function; } else if (supports_ari(fd, prg_p->bus_no) == SUCCESS) { multi_function_device = B_TRUE; if (!(input_args_p->flags & DEV_SPEC_FLAG)) last_func = 255; } /* * Loop through at least func=first_func. Continue looping through * functions if there are no errors and the device is a multi-function * device. * * (Note, if first_func == 0, header will show whether multifunction * device and set multi_function_device. If first_func != 0, then we * will force the loop as the user wants a specific function to be * checked. */ for (func = first_func; ((func <= last_func) && ((func == first_func) || (multi_function_device))); func++) { if (last_func > 7) { prg_p->func_no = func & 0x7; prg_p->dev_no = (func >> 3) & 0x1f; } else prg_p->func_no = func; /* * Four things can happen here: * * 1) ioctl comes back as EFAULT and prg_p->status is * PCITOOL_INVALID_ADDRESS. There is no device at this * location. * * 2) ioctl comes back successful and the data comes back as * zero. Config space is mapped but no device responded. * * 3) ioctl comes back successful and the data comes back as * non-zero. We've found a device. * * 4) Some other error occurs in an ioctl. */ prg_p->status = PCITOOL_SUCCESS; prg_p->offset = 0; prg_p->data = 0; prg_p->user_version = PCITOOL_VERSION; if (((rval = ioctl(fd, PCITOOL_DEVICE_GET_REG, prg_p)) != 0) || (prg_p->data == 0xffffffff)) { /* * Accept errno == EINVAL along with status of * PCITOOL_OUT_OF_RANGE because some systems * don't implement the full range of config space. * Leave the loop quietly in this case. */ if ((errno == EINVAL) || (prg_p->status == PCITOOL_OUT_OF_RANGE)) { break; } /* * Exit silently with ENXIO as this means that there are * no devices under the pci root nexus. */ else if ((errno == ENXIO) && (prg_p->status == PCITOOL_IO_ERROR)) { break; } /* * Expect errno == EFAULT along with status of * PCITOOL_INVALID_ADDRESS because there won't be * devices at each stop. Quit on any other error. */ else if (((errno != EFAULT) || (prg_p->status != PCITOOL_INVALID_ADDRESS)) && (prg_p->data != 0xffffffff)) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Ioctl error: %s\n", strerror(errno)); } break; /* * If no function at this location, * just advance to the next function. */ } else { rval = SUCCESS; } /* * Data came back as 0. * Treat as unresponsive device amd check next device. */ } else if (prg_p->data == 0) { rval = SUCCESS; /* Found something. */ } else { config_hdr.dwords[0] = (uint32_t)prg_p->data; /* Get the rest of the PCI header. */ if ((rval = get_config_header(fd, prg_p->bus_no, prg_p->dev_no, prg_p->func_no, &config_hdr)) != SUCCESS) { break; } /* Print the found information. */ print_probe_info(&config_hdr, prg_p, IS_VERBOSE(input_args_p->flags)); /* * Special case for the type of Southbridge found on * Ultra-45 and other sun4u fire workstations. */ if ((config_hdr.dwords[0] == U45_SB_DEVID_VID) && (config_hdr.dwords[2] == U45_SB_CLASS_RID)) { rval = ECANCELED; break; } /* * Accomodate devices which state their * multi-functionality only in their function 0 config * space. Note multi-functionality throughout probing * of all of this device's functions. */ if (config_hdr.bytes[PCI_CONF_HEADER] & PCI_HEADER_MULTI) { multi_function_device = B_TRUE; } } } return (rval); } /* * Probe a given nexus config space for devices. * * fd is the file descriptor of the nexus. * input_args contains commandline options as specified by the user. */ static int do_probe(int fd, di_node_t di_node, di_prom_handle_t di_phdl, pcitool_uiargs_t *input_args_p) { pcitool_reg_t prg; int bus; int dev; int last_bus = PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT; int last_dev = PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT; int first_bus = 0; int first_dev = 0; int rval = SUCCESS; prg.barnum = 0; /* Config space. */ /* Must read in 4-byte quantities. */ prg.acc_attr = PCITOOL_ACC_ATTR_SIZE_4 + NATIVE_ENDIAN; prg.data = 0; /* If an explicit bus was specified by the user, go with it. */ if (input_args_p->flags & BUS_SPEC_FLAG) { first_bus = last_bus = input_args_p->bus; } else if (input_args_p->flags & PROBERNG_FLAG) { /* Otherwise get the bus range from properties. */ int len; uint32_t *rangebuf = NULL; len = di_prop_lookup_ints(DDI_DEV_T_ANY, di_node, "bus-range", (int **)&rangebuf); /* Try PROM property */ if (len <= 0) { len = di_prom_prop_lookup_ints(di_phdl, di_node, "bus-range", (int **)&rangebuf); } /* Take full range for default if cannot get property. */ if (len > 0) { first_bus = rangebuf[0]; last_bus = rangebuf[1]; } } /* Take full range for default if not PROBERNG and not BUS_SPEC. */ if (last_bus == first_bus) { if (input_args_p->flags & DEV_SPEC_FLAG) { /* Explicit device given. Not probing a whole bus. */ (void) puts(""); } else { (void) printf("*********** Probing bus %x " "***********\n\n", first_bus); } } else { (void) printf("*********** Probing buses %x through %x " "***********\n\n", first_bus, last_bus); } /* Print header. */ print_probe_info(NULL, NULL, IS_VERBOSE(input_args_p->flags)); /* Device number explicitly specified. */ if (input_args_p->flags & DEV_SPEC_FLAG) { first_dev = last_dev = input_args_p->device; } /* * Loop through all valid bus / dev / func combinations to check for * all devices, with the following exceptions: * * When nothing is found at function 0 of a bus / dev combination, skip * the other functions of that bus / dev combination. * * When a found device's function 0 is probed and it is determined that * it is not a multifunction device, skip probing of that device's * other functions. */ for (bus = first_bus; ((bus <= last_bus) && (rval == SUCCESS)); bus++) { prg.bus_no = bus; /* Device number explicitly specified. */ if (input_args_p->flags & DEV_SPEC_FLAG) { first_dev = last_dev = input_args_p->device; } else if (supports_ari(fd, bus) == SUCCESS) { last_dev = 0; first_dev = 0; } else { last_dev = PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT; } for (dev = first_dev; ((dev <= last_dev) && (rval == SUCCESS)); dev++) { prg.dev_no = dev; rval = probe_dev(fd, &prg, input_args_p); } /* * Ultra-45 southbridge workaround: * ECANCELED tells to skip to the next bus. */ if (rval == ECANCELED) { rval = SUCCESS; } } return (rval); } /* * This function is called-back from di_walk_minor() when any PROBE is processed */ /*ARGSUSED*/ static int process_nexus_node(di_node_t di_node, di_minor_t minor, void *arg) { int fd; char *trunc; probe_walk_args_t *walk_args_p = (probe_walk_args_t *)arg; char *pathname = walk_args_p->pathname; char *nexus_path = di_devfs_minor_path(minor); if (nexus_path == NULL) { (void) fprintf(stderr, "Error getting nexus path: %s\n", strerror(errno)); return (DI_WALK_CONTINUE); } /* * Display this node if pathname not specified (as all nodes are * displayed) or if the current node matches the single specified * pathname. Pathname form: xxx, nexus form: xxx:reg */ if ((pathname != NULL) && ((strstr(nexus_path, pathname) != nexus_path) || (strlen(nexus_path) != (strlen(pathname) + strlen(PCI_MINOR_REG) + 1)))) { di_devfs_path_free(nexus_path); return (DI_WALK_CONTINUE); } if ((fd = open_node(nexus_path, walk_args_p->input_args_p)) >= 0) { /* Strip off the suffix at the end of the nexus path. */ if ((trunc = strstr(nexus_path, PCI_MINOR_REG)) != NULL) { trunc--; /* Get the : just before too. */ *trunc = '\0'; } /* Show header only if no explicit nexus node name given. */ (void) puts(""); if (pathname == NULL) { (void) printf("********** Devices in tree under %s " "**********\n", nexus_path); } /* * Exit silently with ENXIO as this means that there are * no devices under the pci root nexus. */ if ((do_probe(fd, di_node, walk_args_p->di_phdl, walk_args_p->input_args_p) != SUCCESS) && (errno != ENXIO)) { (void) fprintf(stderr, "Error probing node %s: %s\n", nexus_path, strerror(errno)); } (void) close(fd); } di_devfs_path_free(nexus_path); /* * If node was explicitly specified, it has just been displayed * and no more looping is required. * Otherwise, keep looping for more nodes. */ return ((pathname == NULL) ? DI_WALK_CONTINUE : DI_WALK_TERMINATE); } /* * Start of probe. If pathname is NULL, search all devices. * * di_walk_minor() walks all DDI_NT_REGACC (PCItool register access) nodes * and calls process_nexus_node on them. process_nexus_node will then check * the pathname for a match, unless it is NULL which works like a wildcard. */ static int do_probe_walk(pcitool_uiargs_t *input_args_p, char *pathname) { di_node_t di_node; di_prom_handle_t di_phdl = DI_PROM_HANDLE_NIL; probe_walk_args_t walk_args; int rval = SUCCESS; if ((di_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { (void) fprintf(stderr, "di_init() failed: %s\n", strerror(errno)); rval = errno; } else if ((input_args_p->flags & PROBERNG_FLAG) && ((di_phdl = di_prom_init()) == DI_PROM_HANDLE_NIL)) { (void) fprintf(stderr, "di_prom_init failed: %s\n", strerror(errno)); rval = errno; } else { walk_args.input_args_p = input_args_p; walk_args.di_phdl = di_phdl; walk_args.pathname = pathname; (void) di_walk_minor(di_node, DDI_NT_REGACC, 0, &walk_args, process_nexus_node); } if (di_phdl != DI_PROM_HANDLE_NIL) { di_prom_fini(di_phdl); } if (di_node != DI_NODE_NIL) { di_fini(di_node); } return (rval); } /* **************** Byte dump specific **************** */ static void print_bytedump_header(boolean_t do_chardump) { static char header1[] = {" " "0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00"}; static char header2[] = {" " "-----------------------------------------------"}; static char cheader1[] = {" 0123456789ABCDEF"}; static char cheader2[] = {" ----------------"}; (void) puts(""); (void) printf(header1); if (do_chardump) { (void) printf(cheader1); } (void) puts(""); (void) printf(header2); if (do_chardump) { (void) printf(cheader2); } } /* Number of bytes per line in a dump. */ #define DUMP_BUF_SIZE 16 #define LINES_BTWN_HEADER 16 /* * Retrieve several bytes over several reads, and print a formatted byte-dump * * fd is the nexus by which device is accessed. * prg provided has bus, dev, func, bank, initial offset already specified, * as well as size and endian attributes. * * No checking is made that this is a read operation, although only read * operations are allowed. */ static int bytedump_get(int fd, int cmd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p) { typedef union { uint8_t bytes[DUMP_BUF_SIZE]; uint16_t shorts[DUMP_BUF_SIZE / sizeof (uint16_t)]; uint32_t dwords[DUMP_BUF_SIZE / sizeof (uint32_t)]; uint64_t longs[DUMP_BUF_SIZE / sizeof (uint64_t)]; } buffer_t; /* * Local copy of pcitool_reg_t, since offset and phys_addrs are * modified. */ pcitool_reg_t local_prg; /* Loop parameters. */ uint32_t dump_end = prg_p->offset + input_args_p->bytedump_amt; uint32_t dump_curr = prg_p->offset; int read_size = input_args_p->size; /* How many stores to the buffer before it is full. */ int wrap_size = DUMP_BUF_SIZE / read_size; /* Address prints at the beginning of each line. */ uint64_t print_addr = 0; /* Skip this num bytes at the beginning of the first dump. */ int skip_begin; /* Skip this num bytes at the end of the last dump. */ int skip_end = 0; /* skip_begin and skip_end are needed twice. */ int skip_begin2; int skip_end2; /* Number of lines between headers */ int lines_since_header = 0; boolean_t do_chardump = input_args_p->flags & CHARDUMP_FLAG; boolean_t continue_on_errs = input_args_p->flags & ERRCONT_FLAG; int rval = SUCCESS; /* Return status. */ int next; int i; buffer_t buffer; uint16_t error_mask = 0; /* 1 bit/byte in buf. Err when set */ bzero(buffer.bytes, sizeof (uint8_t) * DUMP_BUF_SIZE); local_prg = *prg_p; /* Make local copy. */ /* * Flip the bytes to proper order if reading on a big endian machine. * Do this by reading big as little and vs. */ #if (NATIVE_ENDIAN == PCITOOL_ACC_ATTR_ENDN_BIG) local_prg.acc_attr = (PCITOOL_ACC_IS_BIG_ENDIAN(local_prg.acc_attr) ? (local_prg.acc_attr & ~PCITOOL_ACC_ATTR_ENDN_BIG) : (local_prg.acc_attr | PCITOOL_ACC_ATTR_ENDN_BIG)); #endif /* * Get offset into buffer for first store. Assumes the buffer size is * a multiple of the read size. "next" is the next buffer index to do * a store. */ skip_begin = local_prg.offset % DUMP_BUF_SIZE; next = skip_begin / read_size; print_bytedump_header(do_chardump); while (dump_curr < dump_end) { /* For reading from the next location. */ local_prg.offset = dump_curr; /* Access the device. Abort on error. */ if (((rval = ioctl(fd, cmd, &local_prg)) != SUCCESS) && (!(continue_on_errs))) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Ioctl failed:\n errno: %s\n status: %s\n", strerror(errno), strstatus(local_prg.status)); } break; } /* * Initialize print_addr first time through, in case printing * is starting in the middle of the buffer. Also reinitialize * when wrap. */ if (print_addr == 0) { /* * X86 config space doesn't return phys addr. * Use offset instead in this case. */ if (local_prg.phys_addr == 0) { /* No phys addr ret */ print_addr = local_prg.offset - (local_prg.offset % DUMP_BUF_SIZE); } else { print_addr = local_prg.phys_addr - (local_prg.phys_addr % DUMP_BUF_SIZE); } } /* * Read error occurred. * Shift the right number of error bits ((1 << read_size) - 1) * into the right place (next * read_size) */ if (rval != SUCCESS) { /* Read error occurred */ error_mask |= ((1 << read_size) - 1) << (next * read_size); } else { /* Save data to the buffer. */ switch (read_size) { case 1: buffer.bytes[next] = (uint8_t)local_prg.data; break; case 2: buffer.shorts[next] = (uint16_t)local_prg.data; break; case 4: buffer.dwords[next] = (uint32_t)local_prg.data; break; case 8: buffer.longs[next] = (uint64_t)local_prg.data; break; default: rval = EIO; break; } } next++; /* Increment index for next store, and wrap. */ next %= wrap_size; dump_curr += read_size; /* Zero out the remainder of the buffer if done. */ if (dump_curr >= dump_end) { if (next != 0) { bzero(&buffer.bytes[next * read_size], (wrap_size - next) * read_size); skip_end = (wrap_size - next) * read_size; next = 0; /* For printing below. */ } } /* Dump the buffer if full or if done. */ if (next == 0) { skip_begin2 = skip_begin; skip_end2 = skip_end; (void) printf("\n0x%16.16" PRIx64 ":", print_addr); for (i = DUMP_BUF_SIZE - 1; i >= 0; i--) { if (skip_end) { skip_end--; (void) printf(" --"); } else if (skip_begin > i) { skip_begin--; (void) printf(" --"); } else if (error_mask & (1 << i)) { (void) printf(" XX"); } else { (void) printf(" %2.2x", buffer.bytes[i]); } } if (do_chardump) { (void) putchar(' '); for (i = 0; i < DUMP_BUF_SIZE; i++) { if (skip_begin2) { skip_begin2--; (void) printf("-"); } else if ( (DUMP_BUF_SIZE - skip_end2) <= i) { (void) printf("-"); } else if (error_mask & (1 << i)) { (void) putchar('X'); } else if (isprint(buffer.bytes[i])) { (void) putchar(buffer.bytes[i]); } else { (void) putchar('@'); } } } if ((++lines_since_header == LINES_BTWN_HEADER) && (dump_curr < dump_end)) { lines_since_header = 0; (void) puts(""); print_bytedump_header(do_chardump); } print_addr += DUMP_BUF_SIZE; error_mask = 0; } } (void) printf("\n"); return (rval); } /* ************** Device and nexus access commands ************** */ /* * Helper function to set access attributes. Assumes size is valid. */ static uint32_t set_acc_attr(pcitool_uiargs_t *input_args_p) { uint32_t access_attrs; switch (input_args_p->size) { case 1: access_attrs = PCITOOL_ACC_ATTR_SIZE_1; break; case 2: access_attrs = PCITOOL_ACC_ATTR_SIZE_2; break; case 4: access_attrs = PCITOOL_ACC_ATTR_SIZE_4; break; case 8: access_attrs = PCITOOL_ACC_ATTR_SIZE_8; break; } if (input_args_p->big_endian) { access_attrs |= PCITOOL_ACC_ATTR_ENDN_BIG; } return (access_attrs); } static int do_single_access(int fd, int cmd, pcitool_reg_t *prg_p, pcitool_uiargs_t *input_args_p) { boolean_t is_write = B_FALSE; int rval; switch (cmd) { case PCITOOL_NEXUS_SET_REG: case PCITOOL_DEVICE_SET_REG: is_write = B_TRUE; break; default: break; } /* Do the access. Return on error. */ if ((rval = ioctl(fd, cmd, prg_p)) != SUCCESS) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "%s ioctl failed:\n errno: %s\n status: %s\n", is_write ? "write" : "read", strerror(errno), strstatus(prg_p->status)); } return (rval); } /* Print on all verbose requests. */ if (IS_VERBOSE(input_args_p->flags)) { /* * Return offset on platforms which return phys_addr == 0 * for config space. */ if (prg_p->phys_addr == 0) prg_p->phys_addr = input_args_p->offset; (void) printf("Addr:0x%" PRIx64 ", %d-byte %s endian " "register value: 0x%" PRIx64 "\n", prg_p->phys_addr, input_args_p->size, (input_args_p->big_endian ? "big" : "little"), prg_p->data); /* Non-verbose, read requests. */ } else if (!(is_write)) { (void) printf("0x%" PRIx64 "\n", prg_p->data); } return (rval); } /* * fd is the file descriptor of the nexus to access, either to get its * registers or to access a device through that nexus. * * input args are commandline arguments specified by the user. */ static int do_device_or_nexus(int fd, pcitool_uiargs_t *input_args_p) { pcitool_reg_t prg; /* Request details given to the driver. */ uint32_t write_cmd = 0; /* Command given to the driver. */ uint32_t read_cmd = 0; /* Command given to the driver. */ int rval = SUCCESS; /* Return status. */ if (input_args_p->flags & WRITE_FLAG) { prg.data = input_args_p->write_value; if (input_args_p->flags & NEXUS_FLAG) { write_cmd = PCITOOL_NEXUS_SET_REG; } else { write_cmd = PCITOOL_DEVICE_SET_REG; } } if (input_args_p->flags & READ_FLAG) { if (input_args_p->flags & NEXUS_FLAG) { read_cmd = PCITOOL_NEXUS_GET_REG; } else { read_cmd = PCITOOL_DEVICE_GET_REG; } } /* Finish initializing access details for driver. */ /* * For nexus, barnum is the exact bank number, unless it is 0xFF, which * indicates that it is inactive and a base_address should be read from * the input_args instead. * * For devices, barnum is the offset to the desired BAR, or 0 for * config space. */ if ((input_args_p->flags & (BASE_SPEC_FLAG | NEXUS_FLAG)) == (BASE_SPEC_FLAG | NEXUS_FLAG)) { prg.barnum = PCITOOL_BASE; prg.phys_addr = input_args_p->base_address; } else prg.barnum = input_args_p->bank; prg.offset = input_args_p->offset; prg.acc_attr = set_acc_attr(input_args_p); prg.bus_no = input_args_p->bus; prg.dev_no = input_args_p->device; prg.func_no = input_args_p->function; prg.user_version = PCITOOL_VERSION; do { /* Do a bytedump if desired, or else do single ioctl access. */ if (input_args_p->flags & BYTEDUMP_FLAG) { if (IS_VERBOSE(input_args_p->flags)) { (void) printf( "\nDoing %d-byte %s endian reads:", input_args_p->size, input_args_p->big_endian ? "big" : "little"); } rval = bytedump_get(fd, read_cmd, &prg, input_args_p); } else { /* Single write and/or read. */ if (write_cmd != 0) { rval = do_single_access( fd, write_cmd, &prg, input_args_p); } if ((rval == SUCCESS) && (read_cmd != 0)) { rval = do_single_access( fd, read_cmd, &prg, input_args_p); } } } while ((IS_LOOP(input_args_p->flags)) && (rval == SUCCESS) && (keep_looping)); return (rval != SUCCESS ? errno : SUCCESS); } /* *************** Interrupt routing ************** */ /* * Display interrupt information. * iget is filled in with the info to display */ static void print_intr_info(pcitool_intr_get_t *iget_p) { int i; for (i = 0; i < iget_p->num_devs; i++) { if (iget_p->flags & PCITOOL_INTR_FLAG_GET_MSI) (void) printf("0x%x,0x%x: %-10s%d\t %s\n", iget_p->cpu_id, iget_p->msi & 0xff, iget_p->dev[i].driver_name, iget_p->dev[i].dev_inst, iget_p->dev[i].path); else (void) printf("0x%x,0x%x: %-10s%d\t %s\n", iget_p->cpu_id, iget_p->ino & 0xff, iget_p->dev[i].driver_name, iget_p->dev[i].dev_inst, iget_p->dev[i].path); } } /* * Interrupt command support. * * fd is the file descriptor of the nexus being probed. * input_args are commandline options entered by the user. */ static int get_single_interrupt(int fd, pcitool_intr_get_t **iget_pp, pcitool_uiargs_t *input_args_p) { pcitool_intr_get_t *iget_p = *iget_pp; const char *str_type = NULL; uint32_t intr; if (input_args_p->flags & MSI_SPEC_FLAG) { intr = input_args_p->intr_msi; str_type = "msi"; } else { intr = input_args_p->intr_ino; str_type = "ino"; } /* * Check if interrupts are active on this ino/msi. Get as much * device info as there is room for at the moment. If there * is not enough room for all devices, will call again with a * larger buffer. */ if (ioctl(fd, PCITOOL_DEVICE_GET_INTR, iget_p) != 0) { /* * Let EIO errors silently slip through, as * some inos may not be viewable by design. * We don't want to stop or print an error for these. */ if (errno == EIO) { return (SUCCESS); } if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Ioctl to get %s 0x%x " "info failed: %s\n", str_type, intr, strerror(errno)); if (errno != EFAULT) { (void) fprintf(stderr, "Pcitool status: %s\n", strstatus(iget_p->status)); } } return (errno); } /* Nothing to report for this interrupt. */ if (iget_p->num_devs == 0) { return (SUCCESS); } /* Need more room to return additional device info. */ if (iget_p->num_devs_ret < iget_p->num_devs) { iget_p = *iget_pp = realloc(iget_p, PCITOOL_IGET_SIZE(iget_p->num_devs)); iget_p->num_devs_ret = iget_p->num_devs; if (ioctl(fd, PCITOOL_DEVICE_GET_INTR, iget_p) != 0) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Ioctl to get %s 0x%x" "device info failed: %s\n", str_type, intr, strerror(errno)); if (errno != EFAULT) { (void) fprintf(stderr, "Pcitool status: %s\n", strstatus(iget_p->status)); } } return (errno); } } print_intr_info(iget_p); return (SUCCESS); } #define INIT_NUM_DEVS 0 static int get_interrupts(int fd, pcitool_uiargs_t *input_args_p) { int rval = SUCCESS; /* Return status. */ int ino, cpu_id; /* * Start with a struct with space for info of INIT_NUM_DEVS devs * to be returned. */ pcitool_intr_get_t *iget_p = malloc(PCITOOL_IGET_SIZE(INIT_NUM_DEVS)); iget_p->num_devs_ret = INIT_NUM_DEVS; iget_p->user_version = PCITOOL_VERSION; /* Explicit MSI requested. */ if (input_args_p->flags & MSI_SPEC_FLAG) { iget_p->msi = input_args_p->intr_msi; iget_p->flags = PCITOOL_INTR_FLAG_GET_MSI; rval = get_single_interrupt(fd, &iget_p, input_args_p); /* Return all MSIs. */ } else if (input_args_p->flags & MSI_ALL_FLAG) { pcitool_intr_info_t intr_info; intr_info.flags = PCITOOL_INTR_FLAG_GET_MSI; if (ioctl(fd, PCITOOL_SYSTEM_INTR_INFO, &intr_info) != 0) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "intr info ioctl failed: %s\n", strerror(errno)); } } else { int msi; /* * Search through all interrupts. * Display info on enabled ones. */ for (msi = 0; ((msi < intr_info.num_intr) && (rval == SUCCESS)); msi++) { bzero(iget_p, sizeof (pcitool_intr_get_t)); iget_p->num_devs_ret = INIT_NUM_DEVS; iget_p->user_version = PCITOOL_VERSION; iget_p->flags = PCITOOL_INTR_FLAG_GET_MSI; iget_p->msi = msi; rval = get_single_interrupt( fd, &iget_p, input_args_p); } } /* Explicit INO requested. */ } else if (input_args_p->flags & INO_SPEC_FLAG) { iget_p->ino = input_args_p->intr_ino; iget_p->cpu_id = input_args_p->old_cpu; rval = get_single_interrupt(fd, &iget_p, input_args_p); /* Return all INOs. */ } else if (input_args_p->flags & INO_ALL_FLAG) { pcitool_intr_info_t intr_info; intr_info.flags = 0; if (ioctl(fd, PCITOOL_SYSTEM_INTR_INFO, &intr_info) != 0) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "intr info ioctl failed: %s\n", strerror(errno)); } free(iget_p); return (rval); } /* * Search through all interrupts. * Display info on enabled ones. */ if (intr_info.ctlr_type == PCITOOL_CTLR_TYPE_APIX) { for (cpu_id = 0; ((cpu_id < intr_info.num_cpu) && (rval == SUCCESS)); cpu_id++) { for (ino = 0; ((ino < intr_info.num_intr) && (rval == SUCCESS)); ino++) { bzero(iget_p, sizeof (pcitool_intr_get_t)); iget_p->num_devs_ret = INIT_NUM_DEVS; iget_p->user_version = PCITOOL_VERSION; iget_p->cpu_id = cpu_id; iget_p->ino = ino; rval = get_single_interrupt( fd, &iget_p, input_args_p); } } } else { for (ino = 0; (ino < intr_info.num_intr) && (rval == SUCCESS); ino++) { bzero(iget_p, sizeof (pcitool_intr_get_t)); iget_p->num_devs_ret = INIT_NUM_DEVS; iget_p->user_version = PCITOOL_VERSION; iget_p->cpu_id = input_args_p->old_cpu; iget_p->ino = ino; rval = get_single_interrupt( fd, &iget_p, input_args_p); } } } free(iget_p); return (rval); } static int get_interrupt_ctlr(int fd, pcitool_uiargs_t *input_args_p) { pcitool_intr_info_t intr_info; char *ctlr_type = NULL; int rval = SUCCESS; intr_info.flags = 0; if (ioctl(fd, PCITOOL_SYSTEM_INTR_INFO, &intr_info) != 0) { if (!(IS_QUIET(input_args_p->flags))) { (void) perror("Ioctl to get intr ctlr info failed"); } rval = errno; } else { (void) fputs("Controller type: ", stdout); switch (intr_info.ctlr_type) { case PCITOOL_CTLR_TYPE_RISC: ctlr_type = "RISC"; break; case PCITOOL_CTLR_TYPE_UPPC: ctlr_type = "UPPC"; break; case PCITOOL_CTLR_TYPE_PCPLUSMP: ctlr_type = "PCPLUSMP"; break; case PCITOOL_CTLR_TYPE_APIX: ctlr_type = "APIX"; break; default: break; } if (ctlr_type == NULL) { (void) printf("Unknown or new (%d)", intr_info.ctlr_type); } else { (void) fputs(ctlr_type, stdout); } #ifdef __x86 if (intr_info.ctlr_type == PCITOOL_CTLR_TYPE_PCPLUSMP) (void) printf(", IO APIC version: 0x%x, " "local APIC version: 0x%x\n", PSMAT_IO_APIC_VER(intr_info.ctlr_version), PSMAT_LOCAL_APIC_VER(intr_info.ctlr_version)); else #endif /* __x86 */ (void) printf(", version: %2.2x.%2.2x.%2.2x.%2.2x\n", ((intr_info.ctlr_version >> 24) & 0xff), ((intr_info.ctlr_version >> 16) & 0xff), ((intr_info.ctlr_version >> 8) & 0xff), (intr_info.ctlr_version & 0xff)); } return (rval); } /* * * fd is the file descriptor of the nexus being changed. * input_args are commandline options entered by the user. */ static int set_interrupts(int fd, pcitool_uiargs_t *input_args_p) { pcitool_intr_set_t iset; const char *str_type = NULL; uint32_t intr; int rval = SUCCESS; /* Return status. */ /* Load interrupt number and cpu from commandline. */ if (input_args_p->flags & MSI_SPEC_FLAG) { iset.msi = intr = input_args_p->intr_msi; iset.flags = PCITOOL_INTR_FLAG_SET_MSI; str_type = "msi"; } else { iset.ino = intr = input_args_p->intr_ino; iset.flags = 0; str_type = "ino"; } iset.cpu_id = input_args_p->intr_cpu; iset.old_cpu = input_args_p->old_cpu; iset.user_version = PCITOOL_VERSION; iset.flags |= (input_args_p->flags & SETGRP_FLAG) ? PCITOOL_INTR_FLAG_SET_GROUP : 0; /* Do the deed. */ if (ioctl(fd, PCITOOL_DEVICE_SET_INTR, &iset) != 0) { if (!(IS_QUIET(input_args_p->flags))) { (void) fprintf(stderr, "Ioctl to set %s 0x%x failed: %s\n", str_type, intr, strerror(errno)); (void) fprintf(stderr, "pcitool status: %s\n", strstatus(iset.status)); } rval = errno; } else { if (input_args_p->flags & SETGRP_FLAG) { if (iset.flags == PCITOOL_INTR_FLAG_SET_MSI) (void) printf("0x%x,0x%x => 0x%x,0x%x\n", iset.cpu_id, (input_args_p->intr_msi) & 0xff, input_args_p->intr_cpu, iset.msi); else (void) printf("0x%x,0x%x => 0x%x,0x%x\n", iset.cpu_id, (input_args_p->intr_ino) & 0xff, input_args_p->intr_cpu, iset.ino); } else { if (iset.flags == PCITOOL_INTR_FLAG_SET_MSI) (void) printf("0x%x,0x%x -> 0x%x,0x%x\n", iset.cpu_id, (input_args_p->intr_msi) & 0xff, input_args_p->intr_cpu, iset.msi); else (void) printf("0x%x,0x%x -> 0x%x,0x%x\n", iset.cpu_id, (input_args_p->intr_ino) & 0xff, input_args_p->intr_cpu, iset.ino); } } return (rval); } static int do_interrupts(int fd, pcitool_uiargs_t *input_args_p) { if (input_args_p->flags & READ_FLAG) { int gic_rval = SUCCESS; int gi_rval = SUCCESS; if (input_args_p->flags & SHOWCTLR_FLAG) { gic_rval = get_interrupt_ctlr(fd, input_args_p); } gi_rval = get_interrupts(fd, input_args_p); return ((gi_rval != SUCCESS) ? gi_rval : gic_rval); } else { return (set_interrupts(fd, input_args_p)); } } /* *********** Where it all begins... ************* */ int main(int argc, char **argv) { pcitool_uiargs_t input_args; /* Commandline args. */ int fd; /* Nexus file descriptor. */ int rval = SUCCESS; /* Return status value. */ /* Get commandline args and options from user. */ if (get_commandline_args(argc, argv, &input_args) != SUCCESS) { return (EINVAL); } /* Help. */ if (!(input_args.flags & ALL_COMMANDS)) return (SUCCESS); /* * Probe mode. * Nexus is provided as argv[1] unless PROBEALL mode. */ if (input_args.flags & PROBE_FLAGS) { rval = do_probe_walk(&input_args, ((input_args.flags & PROBEALL_FLAG) ? NULL : argv[1])); } else if ((fd = open_node(argv[1], &input_args)) >= 0) { if (input_args.flags & (NEXUS_FLAG | LEAF_FLAG)) { (void) signal(SIGINT, signal_handler); (void) signal(SIGTERM, signal_handler); rval = do_device_or_nexus(fd, &input_args); } else if (input_args.flags & INTR_FLAG) { rval = do_interrupts(fd, &input_args); } else { /* Should never see this. */ (void) fprintf(stderr, "Nothing to do.\n"); rval = ENOTTY; } (void) close(fd); } return (rval); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Milan Jurik. All rights reserved. * Copyright (c) 2018, Joyent, Inc. */ /* * This is the user interface module for the pcitool. It checks commandline * arguments and options and stores them in a pcitool_uiargs_t structure passed * back to the rest of the program for processing. * * Please see pcitool_usage.c for a complete commandline description. */ #include #include #include #include #include #include #include #include #include #include #include "pcitool_ui.h" /* * Uncomment the following for useful debugging / development options for this * module only. */ /* #define DEBUG 1 */ /* #define STANDALONE 1 */ #define DEVNAME_START_PCI "/pci" #define DEVNAME_START_NIU "/niu" /* Default read/write size when -s not specified. */ #define DEFAULT_SIZE 4 /* For get_value64 */ #define HEX_ONLY B_TRUE #define BASE_BY_PREFIX B_FALSE #define BITS_PER_BYTE 8 /* * This defines which main options can be specified by the user. * Options with colons after them require arguments. */ static char *opt_string = ":n:d:i:m:p:rw:o:s:e:b:vaqlcxgy"; /* This defines options used singly and only by themselves (no nexus). */ static char *no_dev_opt_string = "ahpqv"; static void print_bad_option(char *argv[], int optopt, char *optarg); static boolean_t get_confirmation(void); static int get_value64(char *value_str, uint64_t *value, boolean_t hex_only); static int parse_nexus_opts(char *input, uint64_t *flags_arg, uint8_t *bank_arg, uint64_t *base_addr_arg); static int extract_bdf_arg(char *cvalue, char *fld, uint64_t fld_flag, uint64_t *all_flags, uint8_t *ivalue); static int extract_bdf(char *value, char **bvalue_p, char **dvalue_p, char **fvalue_p); static int parse_device_opts(char *input, uint64_t *flags_arg, uint8_t *bus_arg, uint8_t *device_arg, uint8_t *func_arg, uint8_t *bank_arg); static int parse_ino_opts(char *input, uint64_t *flags_arg, uint32_t *cpu_arg, uint8_t *ino_arg); static int parse_msi_opts(char *input, uint64_t *flags_arg, uint16_t *msi_arg); static int parse_intr_set_opts(char *input, uint64_t *flags_arg, uint32_t *cpu_arg); static int parse_probeone_opts(char *input, uint64_t *flags_arg, uint8_t *bus_arg, uint8_t *device_arg, uint8_t *func_arg); #ifdef DEBUG void dump_struct(pcitool_uiargs_t *dump_this); #endif /* Exported functions. */ /* * Main commandline argument parsing routine. * * Takes argc and argv straight from the commandline. * Returns a pcitool_uiargs_t with flags of options specified, and values * associated with them. */ int get_commandline_args(int argc, char *argv[], pcitool_uiargs_t *parsed_args) { int c; /* Current option being processed. */ boolean_t error = B_FALSE; boolean_t confirm = B_FALSE; uint64_t recv64; /* Needed for getopt(3C) */ extern char *optarg; /* Current commandline string. */ extern int optind; /* Index of current commandline string. */ extern int optopt; /* Option (char) which is missing an operand. */ extern int opterr; /* Set to 0 to disable getopt err reporting. */ opterr = 0; bzero(parsed_args, sizeof (pcitool_uiargs_t)); /* No args. probe mode accounting for bus ranges, nonverbose. */ if (argc == 1) { usage(argv[0]); parsed_args->flags = 0; return (SUCCESS); } /* 1st arg is not a device name. */ if ((strstr(argv[1], DEVNAME_START_PCI) != argv[1]) && (strstr(argv[1], DEVNAME_START_NIU) != argv[1])) { /* Default is to probe all trees accounting for bus ranges. */ parsed_args->flags = PROBEALL_FLAG | PROBERNG_FLAG; /* Loop thru the options until complete or an error is found. */ while (((c = getopt(argc, argv, no_dev_opt_string)) != -1) && (error == B_FALSE)) { switch (c) { /* Help requested. */ case 'h': usage(argv[0]); parsed_args->flags = 0; return (SUCCESS); case 'p': /* Take default probe mode */ break; case 'a': /* * Enable display of ALL bus numbers. * * This takes precidence over PROBERNG as -a * is explicitly specified. */ parsed_args->flags &= ~PROBERNG_FLAG; break; case 'q': parsed_args->flags |= QUIET_FLAG; break; /* Verbose mode for full probe. */ case 'v': parsed_args->flags |= VERBOSE_FLAG; break; default: error = B_TRUE; break; } } /* Check for values straggling at the end of the command. */ if (optind != argc) { (void) fprintf(stderr, "%s: Unrecognized parameter " "at the end of the command.\n", argv[0]); error = B_TRUE; } if (error) { print_bad_option(argv, optopt, optarg); return (FAILURE); } return (SUCCESS); } /* Device node specified on commandline. */ /* Skip argv[1] before continuing below. */ optind++; /* Loop through the options until complete or an error is found. */ while (((c = getopt(argc, argv, opt_string)) != -1) && (error == B_FALSE)) { switch (c) { /* Nexus */ case 'n': if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS)) { (void) fprintf(stderr, "%s: -n set with " "-d, -p or -i or is set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= NEXUS_FLAG; if (parse_nexus_opts(optarg, &parsed_args->flags, &parsed_args->bank, &parsed_args->base_address) != SUCCESS) { (void) fprintf(stderr, "%s: Error parsing -n options\n", argv[0]); error = B_TRUE; break; } break; /* Device (leaf node) */ case 'd': if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS)) { (void) fprintf(stderr, "%s: -d set with " "-n, -p or -i or is set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= LEAF_FLAG; if (parse_device_opts(optarg, &parsed_args->flags, &parsed_args->bus, &parsed_args->device, &parsed_args->function, &parsed_args->bank) != SUCCESS) { (void) fprintf(stderr, "%s: Error parsing -d options\n", argv[0]); error = B_TRUE; break; } break; /* Interrupt */ case 'i': if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS)) { (void) fprintf(stderr, "%s: -i set with -m, " "-n, -d or -p or is set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= INTR_FLAG; /* parse input to get ino value. */ if (parse_ino_opts(optarg, &parsed_args->flags, &parsed_args->old_cpu, &parsed_args->intr_ino) != SUCCESS) { (void) fprintf(stderr, "%s: Error parsing interrupt options\n", argv[0]); error = B_TRUE; } break; /* Interrupt */ case 'm': if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS)) { (void) fprintf(stderr, "%s: -m set with -i, " "-n, -d or -p or is set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= INTR_FLAG; /* parse input to get msi value. */ if (parse_msi_opts(optarg, &parsed_args->flags, &parsed_args->intr_msi) != SUCCESS) { (void) fprintf(stderr, "%s: Error parsing interrupt options\n", argv[0]); error = B_TRUE; } break; /* Probe */ case 'p': if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS)) { (void) fprintf(stderr, "%s: -p set with " "-n, -d or -i or is set twice\n", argv[0]); error = B_TRUE; break; } /* Process -p with no dedicated options to it. */ if (optarg[0] == '-') { optind--; /* Probe given tree observing ranges */ parsed_args->flags |= (PROBETREE_FLAG | PROBERNG_FLAG); continue; } /* parse input to get ino value. */ if (parse_probeone_opts(optarg, &parsed_args->flags, &parsed_args->bus, &parsed_args->device, &parsed_args->function) != SUCCESS) { (void) fprintf(stderr, "%s: Error parsing probe options\n", argv[0]); error = B_TRUE; } else { /* * parse_probeone_opts found options to * set up bdf. */ parsed_args->flags |= PROBEDEV_FLAG; } break; /* Probe all busses */ case 'a': /* Must follow -p, and -p must have no bdf. */ if (!(parsed_args->flags & PROBETREE_FLAG)) { error = B_TRUE; break; } parsed_args->flags &= ~PROBERNG_FLAG; break; /* Read */ case 'r': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG))) { error = B_TRUE; break; } /* * Allow read and write to be set together for now, * since this means write then read back for device and * nexus accesses. Check for this and disallow with * interrupt command later. */ parsed_args->flags |= READ_FLAG; break; /* Write */ case 'w': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG))) { error = B_TRUE; break; } if (parsed_args->flags & WRITE_FLAG) { (void) fprintf(stderr, "%s: -w set twice\n", argv[0]); error = B_TRUE; break; } /* * For device and nexus, get a single register value * to write. */ if (parsed_args->flags & (NEXUS_FLAG | LEAF_FLAG)) { parsed_args->flags |= WRITE_FLAG; if (get_value64(optarg, &parsed_args->write_value, HEX_ONLY) != SUCCESS) { (void) fprintf(stderr, "%s: Error reading value to " "write.\n", argv[0]); error = B_TRUE; break; } /* For interrupt, parse input to get cpu value. */ } else if (parsed_args->flags & INTR_FLAG) { parsed_args->flags |= WRITE_FLAG; if (parse_intr_set_opts(optarg, &parsed_args->flags, &parsed_args->intr_cpu) != SUCCESS) { (void) fprintf(stderr, "%s: Error " "parsing interrupt options.\n", argv[0]); error = B_TRUE; break; } } else { error = B_TRUE; break; } break; /* Offset */ case 'o': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG))) { error = B_TRUE; break; } if (parsed_args->flags & OFFSET_FLAG) { (void) fprintf(stderr, "%s: -o set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= OFFSET_FLAG; if (get_value64(optarg, &recv64, HEX_ONLY) != SUCCESS) { (void) fprintf(stderr, "%s: Error in offset argument\n", argv[0]); error = B_TRUE; break; } parsed_args->offset = (uint32_t)recv64; if (parsed_args->offset != recv64) { (void) fprintf(stderr, "%s: Offset argument " "too large for 32 bits\n", argv[0]); error = B_TRUE; break; } break; /* Size */ case 's': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG))) { error = B_TRUE; break; } if (parsed_args->flags & SIZE_FLAG) { (void) fprintf(stderr, "%s: -s set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= SIZE_FLAG; if (get_value64(optarg, &recv64, HEX_ONLY) != SUCCESS) { (void) fprintf(stderr, "%s: Error in size argument\n", argv[0]); error = B_TRUE; break; } switch (recv64) { case 1: case 2: case 4: case 8: break; default: error = B_TRUE; (void) fprintf(stderr, "%s: Error in size argument\n", argv[0]); break; } parsed_args->size |= (uint8_t)recv64; break; /* Endian. */ case 'e': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG))) { error = B_TRUE; break; } if (parsed_args->flags & ENDIAN_FLAG) { (void) fprintf(stderr, "%s: -e set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= ENDIAN_FLAG; /* Only a single character allowed. */ if (optarg[1] != '\0') { (void) fprintf(stderr, "%s: Error in endian argument\n", argv[0]); error = B_TRUE; break; } switch (optarg[0]) { case 'b': parsed_args->big_endian = B_TRUE; break; case 'l': break; default: (void) fprintf(stderr, "%s: Error in endian argument\n", argv[0]); error = B_TRUE; break; } break; /* (Byte)dump */ case 'b': if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG))) { error = B_TRUE; break; } if (parsed_args->flags & BYTEDUMP_FLAG) { (void) fprintf(stderr, "%s: -b set twice\n", argv[0]); error = B_TRUE; break; } parsed_args->flags |= BYTEDUMP_FLAG; if (get_value64(optarg, &recv64, HEX_ONLY) != SUCCESS) { (void) fprintf(stderr, "%s: Error in " "bytedump argument\n", argv[0]); error = B_TRUE; break; } parsed_args->bytedump_amt = (uint32_t)recv64; if (parsed_args->bytedump_amt != recv64) { (void) fprintf(stderr, "%s: Bytedump amount " "too large for 32 bits\n", argv[0]); error = B_TRUE; break; } break; /* Verbose. */ case 'v': parsed_args->flags |= VERBOSE_FLAG; break; /* * Quiet - no errors reported as messages. * (Status still returned by program, however.) */ case 'q': parsed_args->flags |= QUIET_FLAG; break; /* Loop. */ case 'l': parsed_args->flags |= LOOP_FLAG; break; /* * Dump characters with bytedump (-b). * Show controller info with -i. */ case 'c': if (parsed_args->flags & BYTEDUMP_FLAG) { parsed_args->flags |= CHARDUMP_FLAG; } else if (parsed_args->flags & INTR_FLAG) { parsed_args->flags |= SHOWCTLR_FLAG; } else { error = B_TRUE; } break; /* Continue on errors with bytedump (-b). */ case 'x': if (!(parsed_args->flags & BYTEDUMP_FLAG)) { error = B_TRUE; break; } parsed_args->flags |= ERRCONT_FLAG; break; case 'g': if (!(parsed_args->flags & INTR_FLAG)) { error = B_TRUE; break; } parsed_args->flags |= SETGRP_FLAG; break; /* Take -y as confirmation and don't ask (where applicable). */ case 'y': confirm = B_TRUE; break; /* Option without operand. */ case ':': switch (optopt) { case 'p': /* Allow -p without bdf spec. */ parsed_args->flags |= (PROBETREE_FLAG | PROBERNG_FLAG); break; default: error = B_TRUE; break; } break; /* Unrecognized option. */ case '?': error = B_TRUE; break; } } /* * Commandline has been parsed. Check for errors which can be checked * only after commandline parsing is complete. */ if (!error) { /* Check for values straggling at the end of the command. */ if (optind != argc) { (void) fprintf(stderr, "%s: Unrecognized parameter " "at the end of the command.\n", argv[0]); print_bad_option(argv, optopt, optarg); return (FAILURE); } /* No args other than nexus. Default to probing that nexus */ if (!(parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG | INTR_FLAG | PROBE_FLAGS))) { usage(argv[0]); parsed_args->flags = 0; return (SUCCESS); } /* * Don't allow any options other than all-bus, verbose or * quiet with probe command. Set default probe flags if nexus * or leaf options are not specified. */ if (parsed_args->flags & (PROBETREE_FLAG | PROBEALL_FLAG)) { if (parsed_args->flags & ~(PROBE_FLAGS | QUIET_FLAG | VERBOSE_FLAG)) error = B_TRUE; } /* * Allow only read, write, quiet and verbose flags for * interrupt command. Note that INO_SPEC_FLAG and CPU_SPEC_FLAG * get set for interrupt command. */ if (parsed_args->flags & INTR_FLAG) { if (parsed_args->flags & ~(INTR_FLAG | VERBOSE_FLAG | QUIET_FLAG | READ_FLAG | WRITE_FLAG | SHOWCTLR_FLAG | SETGRP_FLAG | INO_ALL_FLAG | INO_SPEC_FLAG | MSI_ALL_FLAG | MSI_SPEC_FLAG | CPU_SPEC_FLAG)) { (void) fprintf(stderr, "%s: -v, -q, -r, -w, -c " "-g are only options allowed with " "interrupt command.\n", argv[0]); error = B_TRUE; } /* Need cpu and ino values for interrupt set command. */ if ((parsed_args->flags & WRITE_FLAG) && !(parsed_args->flags & CPU_SPEC_FLAG) && !((parsed_args->flags & INO_SPEC_FLAG) || (parsed_args->flags & MSI_SPEC_FLAG))) { (void) fprintf(stderr, "%s: Both cpu and ino/msi must be " "specified explicitly for interrupt " "set command.\n", argv[0]); error = B_TRUE; } /* Intr write and show ctlr flags are incompatible. */ if ((parsed_args->flags & (WRITE_FLAG + SHOWCTLR_FLAG)) == (WRITE_FLAG + SHOWCTLR_FLAG)) { (void) fprintf(stderr, "%s: -w and -c are incompatible for " "interrupt command.\n", argv[0]); error = B_TRUE; } /* Intr setgrp flag valid only for intr writes. */ if ((parsed_args->flags & (WRITE_FLAG + SETGRP_FLAG)) == SETGRP_FLAG) { (void) fprintf(stderr, "%s: -g is incompatible with -r " "for interrupt command.\n", argv[0]); error = B_TRUE; } /* * Disallow read & write together in interrupt command. */ if ((parsed_args->flags & (WRITE_FLAG | READ_FLAG)) == (WRITE_FLAG | READ_FLAG)) { (void) fprintf(stderr, "%s: Only one of -r and " "-w can be specified in " "interrupt command.\n", argv[0]); error = B_TRUE; } } /* Bytedump incompatible with some other options. */ if ((parsed_args->flags & BYTEDUMP_FLAG) && (parsed_args->flags & (WRITE_FLAG | PROBE_FLAGS | INTR_FLAG))) { (void) fprintf(stderr, "%s: -b is incompatible with " "another specified option.\n", argv[0]); error = B_TRUE; } if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG)) { if (!(parsed_args->flags & SIZE_FLAG)) { parsed_args->size = DEFAULT_SIZE; } if ((parsed_args->flags & WRITE_FLAG) && parsed_args->size < sizeof (uint64_t) && (parsed_args->write_value >> (parsed_args->size * BITS_PER_BYTE))) { (void) fprintf(stderr, "%s: Data to write is larger than " "specified size.\n", argv[0]); error = B_TRUE; } } else { /* Looping is compatible only with register cmds. */ if (parsed_args->flags & LOOP_FLAG) { (void) fprintf(stderr, "%s: -l is incompatible " "with given command.\n", argv[0]); error = B_TRUE; } } /* Call out an erroneous -y and then ignore it. */ if ((confirm) && (!(parsed_args->flags & BASE_SPEC_FLAG))) { (void) fprintf(stderr, "%s: -y is incompatible with given command." " Ignoring.\n", argv[0]); } } /* Now fill in the defaults and other holes. */ if (!(error)) { if (!(parsed_args->flags & (READ_FLAG | WRITE_FLAG))) { parsed_args->flags |= READ_FLAG; } if (parsed_args->flags & (LEAF_FLAG | NEXUS_FLAG)) { if (!(parsed_args->flags & ENDIAN_FLAG)) { parsed_args->big_endian = B_FALSE; } } if (parsed_args->flags & BASE_SPEC_FLAG) { if (!confirm) { confirm = get_confirmation(); } if (!confirm) { parsed_args->flags &= ~ALL_COMMANDS; } } /* * As far as other defaults are concerned: * Other fields: bus, device, function, offset, default to * zero. */ } else { /* An error occurred. */ print_bad_option(argv, optopt, optarg); } return (error); } /* Module-private functions. */ static void print_bad_option(char *argv[], int optopt, char *optarg) { /* Illegal option operand */ if (optarg != NULL) { (void) fprintf(stderr, "%s: illegal operand %s specified for option %c\n", argv[0], optarg, optopt); /* Illegal option */ } else if (optopt != 0) { (void) fprintf(stderr, "%s: option %c is illegal or is missing an operand\n", argv[0], optopt); /* getopt wasn't even called. Bad device spec. */ } else { (void) fprintf(stderr, "%s: device spec must start with %s or %s...\n", argv[0], DEVNAME_START_PCI, DEVNAME_START_NIU); } (void) fprintf(stderr, "%s: Type \"%s -h\" to get help on running this program.\n", argv[0], argv[0]); } /* * Warn the user and ask for confirmation. */ static boolean_t get_confirmation() { int i, b; (void) printf("WARNING: This cmd with a bad addr can panic " "the system. Continue [y/n] (n)? "); for (i = 0; ; i++) { b = getchar(); switch (b) { case ' ': case '\t': break; case 'y': case 'Y': return (B_TRUE); default: return (B_FALSE); } } } /* * Given a digit string, return a 64 bit value. * * If the hex_only arg is true, interpret all strings as hex. * Otherwise, interpret as strtoull(3C) does with base=0. */ static int get_value64(char *value_str, uint64_t *value, boolean_t hex_only) { /* This is overkill for now, as everything is in hex. */ static char dec_digits[] = "0123456789"; static char hex_digits[] = "01234567890abcdefABCDEF"; static char oct_digits[] = "01234567"; char *digit_string; char *string_to_check; if ((value_str == NULL) || (strlen(value_str) == 0)) { (void) fprintf(stderr, "Missing value argument.\n"); return (FAILURE); } if (!hex_only && (value_str[0] != '0')) { digit_string = dec_digits; string_to_check = value_str; } else if ((value_str[1] == 'X') || (value_str[1] == 'x')) { digit_string = hex_digits; string_to_check = &value_str[2]; /* Ignore 0x of hex */ } else if (hex_only) { digit_string = hex_digits; string_to_check = value_str; /* Hex number, no 0x prefix */ } else { digit_string = oct_digits; string_to_check = value_str; } /* * Verify value is all proper digits. * * For some reason, strtoull doesn't return an error when it cannot * interpret the value. This is why we do the checking ourselves. */ if (strspn(string_to_check, digit_string) != strlen(string_to_check)) { (void) fprintf(stderr, "Value must contain only valid digits.\n"); return (FAILURE); } *value = strtoull(value_str, NULL, (hex_only ? 16 : 0)); return (SUCCESS); } /* * Parse nexus options. This includes: * bank=number * * input is what the user specified for the options on the commandline, * flags_arg is modified with the option set, and bank_arg returns the value * specified for bank. */ static int parse_nexus_opts(char *input, uint64_t *flags_arg, uint8_t *bank_arg, uint64_t *base_addr_arg) { enum nexus_opts_index { bank = 0, base }; static char *nexus_opts[] = { "bank", "base", NULL }; char *value; uint64_t recv64; int rval = SUCCESS; if (input == NULL) { (void) fprintf(stderr, "Missing argument.\n"); return (FAILURE); } while ((*input != '\0') && (rval == SUCCESS)) { switch (getsubopt(&input, nexus_opts, &value)) { case bank: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, "The bank or bar arg is " "specified more than once.\n"); rval = FAILURE; break; } if (*flags_arg & BASE_SPEC_FLAG) { (void) fprintf(stderr, "Bank and base address " "cannot both be specified.\n"); rval = FAILURE; break; } if (value == NULL) { (void) fprintf(stderr, "Missing bank value.\n"); rval = FAILURE; break; } if ((rval = get_value64(value, &recv64, HEX_ONLY)) != SUCCESS) { break; } *bank_arg = (uint8_t)recv64; if (*bank_arg != recv64) { (void) fprintf(stderr, "Bank argument must fit into 8 bits.\n"); rval = FAILURE; break; } *flags_arg |= BANK_SPEC_FLAG; break; case base: if (*flags_arg & BASE_SPEC_FLAG) { (void) fprintf(stderr, "The base address " "is specified more than once.\n"); rval = FAILURE; break; } if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, "Bank and base address " "cannot both be specified.\n"); rval = FAILURE; break; } if (value == NULL) { (void) fprintf(stderr, "Missing base addr value.\n"); rval = FAILURE; break; } if ((rval = get_value64(value, base_addr_arg, HEX_ONLY)) != SUCCESS) { break; } *flags_arg |= BASE_SPEC_FLAG; break; default: (void) fprintf(stderr, "Unrecognized option for -n\n"); rval = FAILURE; break; } } return (rval); } static int extract_bdf_arg(char *cvalue, char *fld, uint64_t fld_flag, uint64_t *all_flags, uint8_t *ivalue) { uint64_t recv64; if (*all_flags & fld_flag) { (void) fprintf(stderr, "The %s is specified more than once.\n", fld); return (FAILURE); } if (get_value64(cvalue, &recv64, HEX_ONLY) != SUCCESS) return (FAILURE); *ivalue = (uint8_t)recv64; if (recv64 != *ivalue) { (void) fprintf(stderr, "This program limits the %s argument to 8 bits.\n", fld); (void) fprintf(stderr, "The actual maximum may be " "smaller but cannot be enforced by this program.\n"); return (FAILURE); } *all_flags |= fld_flag; return (SUCCESS); } static int extract_bdf(char *value, char **bvalue_p, char **dvalue_p, char **fvalue_p) { char *strtok_state; char *dummy; static char *separator = "."; *bvalue_p = strtok_r(value, separator, &strtok_state); *dvalue_p = strtok_r(NULL, separator, &strtok_state); *fvalue_p = strtok_r(NULL, separator, &strtok_state); dummy = strtok_r(NULL, separator, &strtok_state); /* Return failure only if too many values specified. */ return ((dummy) ? FAILURE : SUCCESS); } /* * Parse device options. This includes: * bus=number * dev=number * func=number * bank=number * config * bar0 * bar1 * bar2 * bar3 * bar4 * bar5 * rom * * input is what the user specified for the options on the commandline, * flags_arg is modified with the options set, and the rest of the args return * their respective values. */ static int parse_device_opts( char *input, uint64_t *flags_arg, uint8_t *bus_arg, uint8_t *device_arg, uint8_t *func_arg, uint8_t *bank_arg) { /* Needed by getsubopt(3C) */ enum bdf_opts_index { bus = 0, dev = 1, func = 2, bdf = 3, bank = 4, config = 5, bar0 = 6, bar1 = 7, bar2 = 8, bar3 = 9, bar4 = 10, bar5 = 11, rom = 12 }; /* Needed by getsubopt(3C) */ static char *bdf_opts[] = { "bus", "dev", "func", "bdf", "bank", "config", "bar0", "bar1", "bar2", "bar3", "bar4", "bar5", "rom", NULL }; char *value; /* Current suboption being processed. */ uint64_t recv64; /* Temporary value. */ /* This error message is used in many places. */ static char bank_err[] = {"The bank or bar arg is specified more than once.\n"}; int rval = SUCCESS; while ((*input != '\0') && (rval == SUCCESS)) { switch (getsubopt(&input, bdf_opts, &value)) { /* bus=number */ case bdf: { char *bvalue, *dvalue, *fvalue; if ((rval = extract_bdf(value, &bvalue, &dvalue, &fvalue)) != SUCCESS) { break; } if (!bvalue | !dvalue | !fvalue) { break; } if ((rval = extract_bdf_arg(bvalue, "bus", BUS_SPEC_FLAG, flags_arg, bus_arg)) != SUCCESS) { break; } if ((rval = extract_bdf_arg(dvalue, "dev", DEV_SPEC_FLAG, flags_arg, device_arg)) != SUCCESS) { break; } rval = extract_bdf_arg(fvalue, "func", FUNC_SPEC_FLAG, flags_arg, func_arg); break; } case bus: rval = extract_bdf_arg(value, "bus", BUS_SPEC_FLAG, flags_arg, bus_arg); break; /* dev=number */ case dev: rval = extract_bdf_arg(value, "dev", DEV_SPEC_FLAG, flags_arg, device_arg); break; /* func=number */ case func: rval = extract_bdf_arg(value, "func", FUNC_SPEC_FLAG, flags_arg, func_arg); break; /* bank=number */ case bank: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } if ((rval = get_value64(value, &recv64, HEX_ONLY)) != SUCCESS) { break; } *bank_arg = (uint8_t)recv64; if (rval || (*bank_arg != recv64)) { (void) fprintf(stderr, "Bank argument must" " fit into 8 bits.\n"); rval = FAILURE; break; } *flags_arg |= BANK_SPEC_FLAG; break; /* config */ case config: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_CONFIG; *flags_arg |= BANK_SPEC_FLAG; break; /* bar0 */ case bar0: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR0; *flags_arg |= BANK_SPEC_FLAG; break; /* bar1 */ case bar1: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR1; *flags_arg |= BANK_SPEC_FLAG; break; /* bar2 */ case bar2: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR2; *flags_arg |= BANK_SPEC_FLAG; break; /* bar3 */ case bar3: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR3; *flags_arg |= BANK_SPEC_FLAG; break; /* bar4 */ case bar4: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR4; *flags_arg |= BANK_SPEC_FLAG; break; /* bar5 */ case bar5: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_BAR5; *flags_arg |= BANK_SPEC_FLAG; break; /* rom */ case rom: if (*flags_arg & BANK_SPEC_FLAG) { (void) fprintf(stderr, bank_err); rval = FAILURE; break; } *bank_arg = PCITOOL_ROM; *flags_arg |= BANK_SPEC_FLAG; break; default: (void) fprintf(stderr, "Unrecognized option for -d\n"); rval = FAILURE; break; } } /* Bus, dev and func must all be specified. */ if ((*flags_arg & (BUS_SPEC_FLAG | DEV_SPEC_FLAG | FUNC_SPEC_FLAG)) != (BUS_SPEC_FLAG | DEV_SPEC_FLAG | FUNC_SPEC_FLAG)) { rval = FAILURE; /* No bank specified in any way. Default to config space */ } else if ((*flags_arg & BANK_SPEC_FLAG) == 0) { *flags_arg |= BANK_SPEC_FLAG; *bank_arg = PCITOOL_CONFIG; } return (rval); } /* * Parse INO options. This includes: * ino# | all * * input is the string of options to parse. flags_arg returns modified with * specified options set. Other args return their respective values. */ static int parse_ino_opts(char *input, uint64_t *flags_arg, uint32_t *cpu_arg, uint8_t *ino_arg) { uint64_t value; char *charvalue; int rval = SUCCESS; if (strcmp(input, "all") == 0) { *flags_arg |= INO_ALL_FLAG; #ifdef __x86 } else if (strstr(input, ",") == NULL) { (void) fprintf(stderr, "Interrupt format should be .\n"); rval = FAILURE; #else } else if (strstr(input, ",") == NULL) { if ((rval = get_value64(input, &value, HEX_ONLY)) == SUCCESS) *ino_arg = (uint8_t)value; if (*ino_arg != value) { (void) fprintf(stderr, "ino argument must fit into 8 bits.\n"); rval = FAILURE; } else { *flags_arg |= INO_SPEC_FLAG; } #endif } else if (charvalue = strtok(input, ",")) { if ((rval = get_value64(charvalue, &value, HEX_ONLY)) == SUCCESS) { *cpu_arg = (int)value; } input = strtok(NULL, ","); if (input == NULL) { (void) fprintf(stderr, "ino argument is need.\n"); return (FAILURE); } if ((rval = get_value64(input, &value, HEX_ONLY)) == SUCCESS) *ino_arg = (uint8_t)value; if (*ino_arg != value) { (void) fprintf(stderr, "ino argument must fit into 8 bits.\n"); rval = FAILURE; } else { *flags_arg |= INO_SPEC_FLAG; } } else { (void) fprintf(stderr, "Unrecognized option for -i\n"); rval = FAILURE; } return (rval); } /* * Parse MSI options. This includes: * msi# | all * * input is the string of options to parse. flags_arg returns modified with * specified options set. Other args return their respective values. */ static int parse_msi_opts(char *input, uint64_t *flags_arg, uint16_t *msi_arg) { uint64_t value; int rval = SUCCESS; if (strcmp(input, "all") == 0) { *flags_arg |= MSI_ALL_FLAG; } else if (strstr(input, ",") == NULL) { if ((rval = get_value64(input, &value, HEX_ONLY)) == SUCCESS) *msi_arg = (uint16_t)value; if (*msi_arg != value) { (void) fprintf(stderr, "msi argument must fit into 16 bits.\n"); rval = FAILURE; } else { *flags_arg |= MSI_SPEC_FLAG; } } else if (strtok(input, ",")) { input = strtok(NULL, ","); if (input == NULL) { (void) fprintf(stderr, "msi argument is need.\n"); return (FAILURE); } if ((rval = get_value64(input, &value, HEX_ONLY)) == SUCCESS) *msi_arg = (uint16_t)value; if (*msi_arg != value) { (void) fprintf(stderr, "msi argument must fit into 16 bits.\n"); rval = FAILURE; } else { *flags_arg |= MSI_SPEC_FLAG; } } else { (void) fprintf(stderr, "Unrecognized option for -m\n"); rval = FAILURE; } return (rval); } /* * Parse interrupt set options. This includes: * cpu=number * * input is the string of options to parse. flags_arg returns modified with * specified options set. Other args return their respective values. */ static int parse_intr_set_opts(char *input, uint64_t *flags_arg, uint32_t *cpu_arg) { uint64_t value; int rval = SUCCESS; if ((rval = get_value64(input, &value, HEX_ONLY)) == SUCCESS) { if ((long)value > sysconf(_SC_CPUID_MAX)) { (void) fprintf(stderr, "Cpu argument " "exceeds maximum for this system type.\n"); rval = FAILURE; } else { *cpu_arg = (uint32_t)value; *flags_arg |= CPU_SPEC_FLAG; } } else { (void) fprintf(stderr, "Unrecognized option for -i -m -w\n"); rval = FAILURE; } return (rval); } static int parse_probeone_opts( char *input, uint64_t *flags_arg, uint8_t *bus_arg, uint8_t *device_arg, uint8_t *func_arg) { enum p1_bdf_opts_index { bus = 0, dev = 1, func = 2, bdf = 3 }; /* Needed by getsubopt(3C) */ static char *p1_bdf_opts[] = { "bus", "dev", "func", "bdf", NULL }; char *value; /* Current suboption being processed. */ int rval = SUCCESS; while ((*input != '\0') && (rval == SUCCESS)) { switch (getsubopt(&input, p1_bdf_opts, &value)) { /* bus=number */ case bdf: { char *bvalue, *dvalue, *fvalue; if ((rval = extract_bdf(value, &bvalue, &dvalue, &fvalue)) != SUCCESS) { break; } if (bvalue) if ((rval = extract_bdf_arg(bvalue, "bus", BUS_SPEC_FLAG, flags_arg, bus_arg)) != SUCCESS) { break; } if (dvalue) if ((rval = extract_bdf_arg(dvalue, "dev", DEV_SPEC_FLAG, flags_arg, device_arg)) != SUCCESS) { break; } if (fvalue) rval = extract_bdf_arg(fvalue, "func", FUNC_SPEC_FLAG, flags_arg, func_arg); break; } case bus: rval = extract_bdf_arg(value, "bus", BUS_SPEC_FLAG, flags_arg, bus_arg); break; /* dev=number */ case dev: rval = extract_bdf_arg(value, "dev", DEV_SPEC_FLAG, flags_arg, device_arg); break; /* func=number */ case func: rval = extract_bdf_arg(value, "func", FUNC_SPEC_FLAG, flags_arg, func_arg); break; default: (void) fprintf(stderr, "Unrecognized option for -p\n"); rval = FAILURE; break; } } return (rval); } #ifdef DEBUG static void dump_struct(pcitool_uiargs_t *dumpthis) { (void) printf("flags:0x%x\n", dumpthis->flags); (void) printf("bus:%d (0x%x)\n", dumpthis->bus, dumpthis->bus); (void) printf("device:%d (0x%x)\n", dumpthis->device, dumpthis->device); (void) printf("function:%d (0x%x)\n", dumpthis->function, dumpthis->function); (void) printf("write_value:%" PRIu64 " (0x%" PRIx64 ")\n", dumpthis->write_value, dumpthis->write_value); (void) printf("bank:%d (0x%x)\n", dumpthis->bank, dumpthis->bank); (void) printf("offset:%d (0x%x)\n", dumpthis->offset, dumpthis->offset); (void) printf("size:%d, endian:%s\n", dumpthis->size, dumpthis->big_endian ? "BIG" : "little"); (void) printf("ino:%d, cpu:%d\n", dumpthis->intr_ino, dumpthis->intr_cpu); } #ifdef STANDALONE /* Test program for this module. Useful when implementing new options. */ int main(int argc, char *argv[]) { int status; pcitool_uiargs_t parsed_args; status = get_commandline_args(argc, argv, &parsed_args); if (status) { (void) printf("Error getting command.\n"); } dump_struct(&parsed_args); return (SUCCESS); } #endif /* STANDALONE */ #endif /* DEBUG */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _PCITOOL_UI_H #define _PCITOOL_UI_H #ifdef __cplusplus extern "C" { #endif /* * This defines the interface between the pcitool_ui.c module which parses the * commandline options, and the other pcitool modules which process them. */ #define SUCCESS 0 /* This does not conflict with errno values. */ #define FAILURE -1 /* General failure. */ /* * Flags which get set in the flags field of pcitool_uiargs_t. There is a flag * for each option specified on the commandline. */ #define NEXUS_FLAG 0x1 #define LEAF_FLAG 0x2 #define INTR_FLAG 0x4 /* Either -i or -m specified */ #define PROBEDEV_FLAG 0x8 /* Probe a specific device */ #define PROBETREE_FLAG 0x10 /* Probe all devs on a tree */ #define PROBEALL_FLAG 0x20 /* Probe devs on all trees */ #define PROBERNG_FLAG 0x40 /* Probe devs within bus ranges */ /* - mod to PROBEALL and PROBETREE */ #define PROBE_FLAGS (PROBEDEV_FLAG | PROBETREE_FLAG | PROBEALL_FLAG | \ PROBERNG_FLAG) #define ALL_COMMANDS (NEXUS_FLAG | LEAF_FLAG | INTR_FLAG | PROBE_FLAGS) #define READ_FLAG 0x80 #define WRITE_FLAG 0x100 #define OFFSET_FLAG 0x200 #define SIZE_FLAG 0x400 #define ENDIAN_FLAG 0x800 #define BYTEDUMP_FLAG 0x1000 #define CHARDUMP_FLAG 0x2000 #define ERRCONT_FLAG 0x4000 #define VERBOSE_FLAG 0x8000 #define QUIET_FLAG 0x10000 #define LOOP_FLAG 0x20000 #define SHOWCTLR_FLAG 0x40000 #define SETGRP_FLAG 0x80000 /* Values specified by suboption parser. */ #define BANK_SPEC_FLAG (0x10000ULL << 32) #define BASE_SPEC_FLAG (0x20000ULL << 32) #define BUS_SPEC_FLAG (0x40000ULL << 32) #define DEV_SPEC_FLAG (0x80000ULL << 32) #define FUNC_SPEC_FLAG (0x100000ULL << 32) #define CPU_SPEC_FLAG (0x200000ULL << 32) /* -w */ #define INO_ALL_FLAG (0x400000ULL << 32) /* -i all */ #define INO_SPEC_FLAG (0x800000ULL << 32) /* -i <#ino> */ #define MSI_ALL_FLAG (0x1000000ULL << 32) /* -m all */ #define MSI_SPEC_FLAG (0x2000000ULL << 32) /* -m <#msi> */ /* Macros for a few heavily-used flags. */ #define IS_VERBOSE(flags) (flags & VERBOSE_FLAG) #define IS_QUIET(flags) (flags & QUIET_FLAG) #define IS_LOOP(flags) (flags & LOOP_FLAG) /* * This is the structure of flags and parsed values returned from pcitool_ui.c */ typedef struct uiargs { uint64_t write_value; uint64_t base_address; uint64_t flags; uint32_t offset; uint32_t bytedump_amt; uint32_t intr_cpu; uint32_t old_cpu; uint8_t bus; uint8_t device; uint8_t function; uint8_t size; uint8_t bank; uint8_t intr_ino; uint16_t intr_msi; boolean_t big_endian; } pcitool_uiargs_t; /* Exported functions. */ int get_commandline_args(int argc, char *argv[], pcitool_uiargs_t *parsed_args); void usage(char *name); #ifdef __cplusplus } #endif #endif /* _PCITOOL_UI_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include static char *pcitool_usage_text[] = { "Usage:", "Probe mode:", " %s [ ] [ -a ] [ -p ] [ -v ] [ -q ]", "", " %s [ -p [ bus=,dev=,func= ] [ -v ] [ -q ]", " %s [ -p [ bdf=.. ] [ -v ] [-q ]", "", "Register peek/poke mode:", " %s ", " [ -n bank=", " where register bank is 0 for mapped jbus space and 1 for pcie space", " (only on applicable platforms)", "", " -n base=", " where base address is a physical base address of a register bank", " (only on applicable platforms (e.g. sun4v) where bank info is " "unavailable)", "", " -d bus=,dev=,func=,bank=", " -d bdf=..,bank=", "", " -d bus=,dev=,func= | bdf=.. ,", " [ config | bar0 | bar1 | bar2 | bar3 | bar4 | bar5 | rom ]", " (spaces added for clarity but are not allowed in the command)", "", " -n and -d options may also include:", "", " [ -w ] [ -r ]", " [ -o ]", " [ -s 1 | 2 | 4 | 8 ]", " [ -e b | l ]", " [ -l ]", " [ -b [ -c ] [ -x ] ]", " [ -v ]", " [ -q ]", "", " -n may also include:", "", " [ -y ]", "", "Interrupt mode:", " X86:", " %s pci@ -i | all", " [ -r [ -c ] | -w [ -g ] ] [ -v ] [ -q ]", " SPARC:", " %s pci@ -i | all", " [ -r [ -c ] | -w [ -g ] ] [ -v ] [ -q ]", " %s pci@ -m | all", " [ -r [ -c ] | -w [ -g ] ] [ -v ] [ -q ]", "", "where", "", "pci@ is a node from /devices, with \"/devices\" stripped off.", "For example: /pci@0,0", "", "-v gives verbose output for all modes.", "", "-q suppresses error output (except for commandline parsing errors) for all " "modes", " (Note that errno status is returned to the shell upon termination.)", "", "Online help mode:", " %s -h", " Prints this message.", "", "Probe mode", "----------", "", "-p [ bus=,dev=,func= | bdf=.. ]", " Specify bus, device and/or function of devices to search for and dump.", "", "-a Probe all buses. By default, pcitool checks the PCI nexus node's", "bus-range property to restrict which buses are probed. This option", "cannot be combined with an explicit bus specification.", "", "If a PCI nexus node is specified, pcitool looks only for devices", "under that node. If no PCI nexus node is specified, pcitool looks", "for devices under all PCI nexus nodes. PCI nexus nodes, which can", "be used for other pcitool commands, are printed at the top of each tree.", "", "Non-verbose probe mode prints config register data as follows:", " aa bb c dddd eeee ffff gggg hh iiiiii jj kk ll mm", " where...", " a = pci bus number", " b = pci device number", " c = pci function number", " d = vendor ID", " e = device ID", " f = command register", " g = status register", " h = revision ID", " i = class code", " j = cache line size", " k = latency timer", " l = header type", " m = built in self test register (bist)", "", "Register peek/poke mode", "-----------------------", "", "-n requests nexus node info.", " Specify desired nexus register using -o ", "", "-d requests device (leaf) node info.", " Specify bus, dev, function bits (from probe mode) as hex numbers.", " Bank is specified in one of the following ways:", " By value: 0 == config space, 1 == BAR0, 2 == BAR1, ..., 6 == BAR5, " "7 == ROM", " By BAR (bus addr reg): config, bar0, bar1, bar2, bar3, bar4, bar5, rom", "", "Above peek/poke mode selections take the following options:", "", "-r for reading (default)", "-w for writing", "-w -r for writing a value followed by a readback", "", "-o to specify an offset within the requested address space", "", "-s : 1, 2, 4 or 8 bytes, default 4", " (8-byte transfers on supported platforms only)", "", "-e : b or l (ell), default is l for little endian>", "", "-l to do repetitious accesses to/from the same location(s)", "", "-b [ -c ] to get a formatted multiple register dump", " starting at the offset given. Hex bytes are always dumped.", " -c dumps characters as well. " "Non-printable characters are dumped as \"@\".", " -x keeps going on errors, and prints err characters as X", "", "Above nexus peek/poke mode selections take the following additional option:", "", "-y to confirm a base_addr without being prompted interactively", "", "NOTE: Some platforms (i.e. SPARC) return peek/poke errors as failed ioctls;", " Other platforms (i.e. X86) return peek/poke errors as FF values.", "", "All numeric values are in HEX", "", "Interrupt mode", "--------------", "", "-i <[cpu#],ino#> changes or retrieves current interrupts information of given", " nexus and given INO. The special value of 'all' can be used to select all", " INOs.", "", "-m <[cpu#],msi#> changes or retrieves current interrupts information of given", " nexus and given MSI/X. The special value of 'all' can be used to select", " all MSI/Xs.", "", " Note: [cpu#] is available on x86 platform, is to identify exclusive vector", " with ino# at the same time. [cpu#] is not supported on SPARC platform.", "", " Note: On x86 platforms, both INOs and MSI/Xs are mapped to the same", " interrupt vectors. Use -i option to retrieve and reroute any interrupt", " vectors (both INO and MSI/Xs). So, -m option is not required on x86", " platforms. Hence it is not supported.", "", " A specific INO or MSI/X must be selected if -w specified.", "", "-w [ -g ] to change an INO or MSI/X <->CPU binding.", "", " Note: On certain platforms (e.g. X86), multiple MSI interrupts of a single", " function need to be moved together. Use -g to do this. -g works only on", " supported platforms and only for groups of MSI interrupts. When -g is", " used, INO must be the lowest-numbered vector of the group. (Use the mdb", " ::interrupts dcmd to discover groups of MSI vectors.) The size of the", " group is determined internally. (\"Groups\" of size 1 are accepted.)", "", "-r [ -c ] for displaying ino or msi <->CPU bindings of all selected INO/MSIs", " on a given nexus. -c optionally dumps controller information.", "", " All relevant enabled INO/MSI/Xs supporting non-nexus device interrupts", " will be printed. For each printed INO/MSI/X, all supported devices and", " their CPU binding will be displayed. On some platforms, INOs dedicated", " to the root nexus will be shown and marked with \"(Internal)\".", "", "When neither -r nor -w are specified, -r is the default.", NULL }; /* * Print usage statement. * * Text is too large for many print statements. * * Instead, loop through the array of strings in pcitool_usage_text. * Print program name when %s is in the text. */ void usage(char *name) { int i; for (i = 0; pcitool_usage_text[i] != NULL; i++) { (void) printf(pcitool_usage_text[i], name); (void) printf("\n"); } }