OpenSolaris_b135/cmd/pcitool/pcitool_ui.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inttypes.h>
#include <sys/types.h>
#include <sys/param.h>
#include <strings.h>
#include <errno.h>
#include <sys/pci.h>

#include <sys/pci_tools.h>

#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, 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->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);
			break;
		default:
			return (B_FALSE);
			break;
		}
	}
}


/*
 * 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)
{
	typedef enum {
		bank = 0,
		base
	} nexus_opts_index_t;

	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) */
	typedef enum {
		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
	} bdf_opts_index_t;

	/* 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, uint8_t *ino_arg)
{
	uint64_t	value;
	int		rval = SUCCESS;

	if (strcmp(input, "all") == 0) {
		*flags_arg |= INO_ALL_FLAG;
	} else 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 ((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)
{
	typedef enum {
		bus = 0,
		dev = 1,
		func = 2,
		bdf = 3
	} p1_bdf_opts_index_t;

	/* 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 */