pcitool.c revision d5ace9454616652a717c9831d949dffa319381f9
/*
* 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
* 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.
*/
/* This file is the main module for the pcitool. */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inttypes.h>
#include <signal.h>
#include <fcntl.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>
#include <libdevinfo.h>
#ifdef __x86
#include <sys/apic_ctlr.h>
#endif
#include <sys/pci_tools.h>
#include "pcitool_ui.h"
/* First 16 longs of device PCI config header. */
typedef union {
/* Used by probe printing functions. */
typedef struct {
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 {
char *pathname;
/*
* 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)
#else
#error "ISA is neither __sparc nor __x86"
#endif
/* status error lookup table. */
static struct {
char *string;
} pcitool_stat_str[] = {
"No error status returned from driver" },
"CPU is non-existent or not online" },
"INO is out of range or invalid" },
"Timeout waiting for pending interrupts to clear" },
"Reg property has invalid format" },
"Address out of range or invalid" },
"Improper address alignment for access attempted" },
"Argument out of range" },
"End of address range" },
"Device ROM is disabled. Cannot read" },
"Write to ROM not allowed" },
"IO error encountered" },
"Size is invalid for this platform" },
{ 0, NULL }
};
/* Used with ^C handler to stop looping in repeat mode in do_device_or_nexus. */
static void signal_handler(int dummy);
/* *************** General ************** */
/*
* Handler for ^C to stop looping.
*/
/*ARGSUSED*/
static void
signal_handler(int dummy)
{
}
/*
* Print string based on PCItool status returned from driver.
*/
static char *
{
int i;
return (pcitool_stat_str[i].string);
}
}
return ("Unknown status returned from driver.");
}
static int
{
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. */
suffix = "";
} else {
}
} else {
suffix = "";
} else {
}
}
/*
* Build nexus pathname.
* for interrupt nodes, and ...:reg for register nodes.
*
* ...The 2 at the end leaves room for a : and the terminating NULL.
*/
/*LINTED*/
/* Open the nexus. */
"Could not open nexus node %s: %s\n",
}
}
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[] = {
};
/* Header 0 (for regular devices) have these fields. */
static field_type_t last_dev_fields[] = {
};
/* Header 1 (PCI-PCI bridge devices) have these fields. */
static field_type_t last_pcibrg_fields[] = {
};
/* Header 2 (PCI-Cardbus bridge devices) have these fields. */
static field_type_t last_cbbrg_fields[] = {
};
#define FMT_SIZE 7
static void
{
/* Size cannot be any larger than 4 bytes. This is not checked. */
/* Build format of print, "%<size*2>.<size*2>x" */
while (size-- > 0) {
}
/*LINTED*/
}
static void
{
int i;
(void) printf("\n"
"Bus Number: %x Device Number: %x Function Number: %x\n",
}
case PCI_HEADER_ZERO: /* Header type 0 is a regular device. */
break;
case PCI_HEADER_PPB: /* Header type 1 is a PCI-PCI bridge. */
(void) printf("PCI-PCI bridge\n");
break;
case PCI_HEADER_CARDBUS: /* Header type 2 is a cardbus bridge */
(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) putchar('\n');
}
for (i = 0; last_fields[i].size != 0; i++) {
(void) putchar('\n');
}
}
}
static void
{
int i;
(void) printf("%2.2x %2.2x %1.1x ",
for (i = 0; first_fields[i].size != 0; i++) {
}
(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
{
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) {
} else {
}
}
/*
* 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
{
int i;
/* Prepare a local pcitool_reg_t so as to not disturb the caller's. */
/* Get dwords 1-15 of config space. They must be read as uint32_t. */
if ((rval =
break;
}
}
return (rval);
}
/*
* 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.
*
* 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
{
int func;
int first_func = 0;
}
/*
* 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.
*/
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.
*/
/*
* 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.
*/
break;
}
/*
* Exit silently with ENXIO as this means that there are
* no devices under the pci root nexus.
*/
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.
*/
"Ioctl error: %s\n",
}
break;
/*
* If no function at this location,
* just advance to the next function.
*/
} else {
}
/*
* Data came back as 0.
* Treat as unresponsive device amd check next device.
*/
break; /* Func loop. */
/* Found something. */
} else {
/* Get the rest of the PCI header. */
SUCCESS) {
break;
}
/* Print the found information. */
/*
* Special case for the type of Southbridge found on
* Ultra-45 and other sun4u fire workstations.
*/
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.
*/
}
}
}
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
{
int bus;
int dev;
int first_bus = 0;
int first_dev = 0;
/* Must read in 4-byte quantities. */
/* If an explicit bus was specified by the user, go with it. */
/* Otherwise get the bus range from properties. */
int len;
"bus-range", (int **)&rangebuf);
/* Try PROM property */
if (len <= 0) {
"bus-range", (int **)&rangebuf);
}
/* Take full range for default if cannot get property. */
if (len > 0) {
}
}
/* Take full range for default if not PROBERNG and not BUS_SPEC. */
/* 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 "
}
/* Print header. */
/* Device number explicitly specified. */
}
/*
* 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.
*/
}
/*
* Ultra-45 southbridge workaround:
* ECANCELED tells to skip to the next bus.
*/
}
}
return (rval);
}
/*
* This function is called-back from di_walk_minor() when any PROBE is processed
*/
/*ARGSUSED*/
static int
{
int fd;
char *trunc;
if (nexus_path == NULL) {
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
*/
(strlen(nexus_path) !=
return (DI_WALK_CONTINUE);
}
/* Strip off the suffix at the end of the nexus path. */
trunc--; /* Get the : just before too. */
*trunc = '\0';
}
/* Show header only if no explicit nexus node name given. */
(void) puts("");
(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 node was explicitly specified, it has just been displayed
* and no more looping is required.
* Otherwise, keep looping for more nodes.
*/
}
/*
* 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
{
} else {
}
if (di_phdl != DI_PROM_HANDLE_NIL) {
}
if (di_node != DI_NODE_NIL) {
}
return (rval);
}
/* **************** Byte dump specific **************** */
static void
{
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("");
if (do_chardump) {
}
(void) puts("");
if (do_chardump) {
}
}
/* 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
{
typedef union {
} buffer_t;
/*
* Local copy of pcitool_reg_t, since offset and phys_addrs are
* modified.
*/
/* Loop parameters. */
/* How many stores to the buffer before it is full. */
/* 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;
int next;
int i;
/*
* 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)
#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.
*/
/* For reading from the next location. */
/* Access the device. Abort on error. */
(!(continue_on_errs))) {
"Ioctl failed:\n errno: %s\n status: %s\n",
}
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.
*/
} else {
}
}
/*
* Read error occurred.
* Shift the right number of error bits ((1 << read_size) - 1)
* into the right place (next * read_size)
*/
error_mask |=
} else { /* Save data to the buffer. */
switch (read_size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
default:
break;
}
}
next++;
/* Increment index for next store, and wrap. */
/* Zero out the remainder of the buffer if done. */
if (next != 0) {
next = 0; /* For printing below. */
}
}
/* Dump the buffer if full or if done. */
if (next == 0) {
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",
}
}
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 {
(void) putchar('@');
}
}
}
if ((++lines_since_header == LINES_BTWN_HEADER) &&
lines_since_header = 0;
(void) puts("");
}
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
{
switch (input_args_p->size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
}
if (input_args_p->big_endian) {
}
return (access_attrs);
}
static int
{
int rval;
switch (cmd) {
case PCITOOL_NEXUS_SET_REG:
case PCITOOL_DEVICE_SET_REG:
break;
default:
break;
}
/* Do the access. Return on error. */
"%s ioctl failed:\n errno: %s\n status: %s\n",
}
return (rval);
}
/* Print on all verbose requests. */
/*
* Return offset on platforms which return phys_addr == 0
* for config space.
*/
/* Non-verbose, read requests. */
} else if (!(is_write)) {
}
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
{
} else {
}
}
} else {
}
}
/* 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.
*/
(BASE_SPEC_FLAG | NEXUS_FLAG)) {
} else
do {
/* Do a bytedump if desired, or else do single ioctl access. */
(void) printf(
"\nDoing %d-byte %s endian reads:",
"big" : "little");
}
} else {
if (write_cmd != 0) {
}
}
}
(keep_looping));
}
/* *************** Interrupt routing ************** */
/*
* Display interrupt information.
* iget is filled in with the info to display
*/
static void
{
int i;
(void) printf("\nino %x mapped to cpu %x\n",
(void) printf(" Driver: %s, instance %d\n",
}
}
/*
* Interrupt command support.
*
* fd is the file descriptor of the nexus being probed.
* input_args are commandline options entered by the user.
*/
static int
{
/*
* Check if interrupts are active on this ino. 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.
*/
/*
* 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.
*/
return (SUCCESS);
}
}
}
return (errno);
}
/* Nothing to report for this interrupt. */
return (SUCCESS);
}
/* Need more room to return additional device info. */
"%d device info failed %s\n",
"Pcitool status: %s\n",
}
}
return (errno);
}
}
return (SUCCESS);
}
#define INIT_NUM_DEVS 0
static int
{
/*
* Start with a struct with space for info of INIT_NUM_DEVS devs
* to be returned.
*/
/* Explicit ino requested. */
} else { /* Return all inos. */
"intr info ioctl failed:%s\n",
}
} else {
int ino;
/*
* Search through all interrupts.
* Display info on enabled ones.
*/
for (ino = 0;
ino++) {
}
}
}
return (rval);
}
static int
{
(void) perror("Ioctl to get intr ctlr info failed");
}
} else {
case PCITOOL_CTLR_TYPE_RISC:
ctlr_type = "RISC";
break;
case PCITOOL_CTLR_TYPE_UPPC:
ctlr_type = "UPPC";
break;
ctlr_type = "PCPLUSMP";
break;
default:
break;
}
(void) printf("Unknown or new (%d)",
} else {
}
#ifdef __x86
(void) printf(", IO APIC version: 0x%x, "
"local APIC version: 0x%x\n",
else
#endif /* __x86 */
(void) printf(", version: %2.2x.%2.2x.%2.2x.%2.2x\n",
}
return (rval);
}
/*
*
* fd is the file descriptor of the nexus being changed.
* input_args are commandline options entered by the user.
*/
static int
{
/* Load interrupt number and cpu from commandline. */
/* Do the deed. */
"Ioctl to set intr 0x%x failed: %s\n",
}
} else {
(void) printf("\nInterrupts on ino %x reassigned:",
} else {
(void) printf("\nInterrupts on ino group starting "
}
}
return (rval);
}
static int
{
int gic_rval;
int gi_rval;
}
} else {
}
}
/* *********** Where it all begins... ************* */
int
{
int fd; /* Nexus file descriptor. */
/* Get commandline args and options from user. */
return (EINVAL);
}
/* Help. */
return (SUCCESS);
/*
* Probe mode.
* Nexus is provided as argv[1] unless PROBEALL mode.
*/
} else {
/* Should never see this. */
}
}
return (rval);
}