/* http://neil.franklin.ch/Projects/Parport/parport.c */
/*   - parallel port utility, set and read port bits (and so port pins) */
/* author Neil Franklin, last modification 2007.10.30 */


/* --- SECURITY WARNING --- */

/* This program must be run as root or SIUD to root, so it can work. */
/* Anything else, such as an other SUID or SGID is not possible, as it uses */
/* an root-only system call, iopl(), to get access to the port(s). */

/* If set SIUD root, any users can use this to directly manipulate anything */ 
/* connected to any parallel port, in any way they wish, by sending any */
/* random signal to it, inclusive crashing or reconfiguring it. This is a */
/* hardware manipulation program after all! */

/* You may want to set it to be only executable by an user group. */

/* Do not use this on an system with any parport modules loaded into the */
/* kernel. Users may be able to confuse drivers by feeding them changed port */
/* pins, and possibly crash the kernel via them. */


/* --- compiling, installing and using --- */

/* compile this as user with:                  */
/*   cc -o parport parport.c                   */
/* install it as root with:                    */
/*   cp -p parport /usr/local/bin              */
/*   chown root:root /usr/local/bin/parport    */
/*   chmod 4755 /usr/local/bin/parport         */
/* or only usable by the group pport with:     */
/*   echo "pport::666:" >> /etc/group          */
/*   cp -p parport /usr/local/bin              */
/*   chown root:pport /usr/local/bin/parport   */
/*   chmod 4750 /usr/local/bin/parport         */
/* help for anyone with:                       */
/*   parport -h                                */


/* --- prerequisites --- */

/* to really understand this code you should */
/*   have an understanding of the basics of the PC parallel port */
/*   see file LED_Tester file for details on what signals are there */


/* --- includes --- */

/* exit() */
#include <stdlib.h>
/* fprintf() printf() sprintf() stderr */
#include <stdio.h>
/* iopl() ioperm() outb inb */
#include <sys/io.h>


/* --- prototypes --- */

int main(int argc, char *argv[], char *env[]);
unsigned short int port(char *name);
unsigned short int reg(char *name);
unsigned char invmask(unsigned short int reg);
unsigned char input(char *string);
unsigned long int utime(char *string);
unsigned char type(char *string);
void output(unsigned char value);
char *format(unsigned char value);
void helptext(FILE *Output);


/* --- for debugging, test runs as non-root --- */

/* debug operation: 1, standard operation: 0 */
#if 0
#define IOPL_EXIT() fprintf(stderr, "DEBUG: exit: continuing despite no access\n")
#define INB(port) 69
#define OUTB(val, port) fprintf(stderr, "DEBUG: outb: %#03hx %#02hhx\n", port, val)
#else
#define IOPL_EXIT() exit(1)
#define INB(port) inb(port)
#define OUTB(value, port) outb(value, port)
#endif


/* --- main program, control --- */

/* for use in error messages */
char *MainProgName;

/* ports are 0x378 (CGA + 1st Multi-IO), 0x278 (2nd Multi-IO), 0x3BD (MDA) */
unsigned short int MainPort = 0x378;
/* registers are 0 (data), 1 (status), 2 (control) */
unsigned short int MainReg = 0;

/* used for XOR inversion, set to 0 for no effect */
unsigned char MainInvertMask = 0;
/* 0 for no inversion, leaving standard hardware inversion */
/* 1 for pre-inverting before writing and un-inverting after reading */
/* can not use MainInvertMask as flag, because it is 0 for -d port */
int MainInvert = 0;

/* for -r output formatting, set by -t */
char MainType = 'd';

/* 0 for standard verbosity, ++ on every -v option, -- on every -q option */
int MainVerbosity = 0;

int main(int argc, char *argv[], char *env[]) {

  MainProgName = *argv++;

  /* get access to IO hardware, ioperm() may be more sensible */
  /*   because of this call, this program must be run as root or SUID root */
  if (iopl(3) == -1) {
    fprintf(stderr, "%s: FATAL: Can not get access to IO port, not running as root user or SUID root?, aborting ...\n", MainProgName);
    IOPL_EXIT(); }

  /* allow mult option group arguments, even though mult options in group */
  while (*argv != NULL && (*argv)[0] == '-') {
    /* this argument is a bunch of options, modify the relevant variables */
    /*   or in case of command "options" execute the relevant command */

    char *option;
    /* can be -xyz multiple options, and jump over the '-' */
    for (option = (*argv++)+1; *option != '\0'; option++) {

      if      (*option == 'p') {
        /* -p (port select): next argument must be a port name, take it */
        MainPort = port(*argv++); }

      else if (*option == 'd' || *option == 's' || *option == 'c') {
        /* -d (data register), -s (status register), -c (control register) */
        MainReg = reg(option);
        /* in case user uses -i before -d|-s|-c */
        if (MainInvert) {
          MainInvertMask = invmask(MainReg); } }

      else if (*option == 'i') {
        /* -i ([pre-|un-]invert) */
        MainInvertMask = invmask(MainReg);
        /* in case user uses -d|-s|-c after -i */
        MainInvert = 1; }

      else if (*option == 'w') {
        /* -w (write to port): next argument must be value, take it */
        unsigned char value = input(*argv++);
        OUTB(value ^ MainInvertMask, MainPort+MainReg); }

      else if (*option == 'a') {
        /* -a (and to port): next argument must be value, take it */
        unsigned char value = INB(MainPort+MainReg) ^ MainInvertMask;
        value &= input(*argv++);
        OUTB(value ^ MainInvertMask, MainPort+MainReg); }

      else if (*option == 'o') {
        /* -o (or to port): next argument must be value, take it */
        unsigned char value = INB(MainPort+MainReg) ^ MainInvertMask;
        value |= input(*argv++);
        OUTB(value ^ MainInvertMask, MainPort+MainReg); }

      else if (*option == 'x') {
        /* -x (xor to port): next argument must be value, take it */
        unsigned char value = INB(MainPort+MainReg) ^ MainInvertMask;
        value ^= input(*argv++);
        OUTB(value ^ MainInvertMask, MainPort+MainReg); }

      else if (*option == 'u') {
        /* -u (usecond sleep): next argument must be time, take it */
        usleep(utime(*argv++)); }

      else if (*option == 't') {
        /* -t (output type): next argument must be format type, take it */
        MainType = type(*argv++); }

      else if (*option == 'r') {
        /* -r (read from port) */
        unsigned char value = INB(MainPort+MainReg) ^ MainInvertMask;
        output(value); }

      else if (*option == 'q') {
        /* -q (quiet) */
        MainVerbosity--; }

      else if (*option == 'v') {
        /* -v (verbose) */
        MainVerbosity++; }

      else if (*option == 'h') {
        /* - (-h (help): output help text, and exit then) */
        helptext(stdout);
        exit(0);; }

      else {
        /* unknown option given, give error message, help, exit with error */
        fprintf(stderr, "%s: ERROR: unknown option: -%c, aborting ...\n",
          MainProgName, *option);
        if (MainVerbosity >= 0) {
          fprintf(stderr, "\n");
          helptext(stderr); }
        exit(1); } } }

  /* there should be no arguments any more, give msg and help and error exit */
  if (*argv != NULL) {
    fprintf(stderr, "%s: ERROR: non opt parameters, at: %s, aborting ...\n",
      MainProgName, *argv);
    if (MainVerbosity >= 0) {
      fprintf(stderr, "\n");
      helptext(stderr); }
    exit(1); }

  /* no actions any more, as all commands are given as command options ! */

  exit(0); }


/* --- parse arguments of some options --- */

/* ports are 0x378 (CGA + 1st Multi-IO), 0x278 (2nd Multi-IO), 0x3BD (MDA) */
unsigned short int port(char *name) {

  unsigned short int port;

  if (!name) {
    fprintf(stderr, "%s: ERROR: -p no port name given, aborting ...\n",
      MainProgName);
    exit(1); }

  /* allow also decimal or octal, in case user does this */
  port = (unsigned short int)strtoul(name, &name, 0);

  if (*name != '\0') {
    fprintf(stderr, "%s: WARNING: not port name, at: '%s', ignoring rest\n",
      MainProgName, name); }

  if (port != 0x378 && port != 0x278 && port != 0x3BD) {
    fprintf(stderr, "%s: ERROR: unknown port: '%#03x', aborting ...\n",
      MainProgName, port);
    exit(1); }

  return(port); }


/* register names are d (data), s (status), c (control) */
unsigned short int reg(char *name) {

  if      (name[0] == 'd') {
    return(0); }
  else if (name[0] == 's') {
    return(1); }
  else if (name[0] == 'c') {
    return(2); } }


/* inverted signals are none (data), busy (status), selin feed stb (control) */
unsigned char invmask(unsigned short int reg) {

  if      (reg == 1) {
    /* status port: busy (bit 7) */
    return(0x80); }
  else if (reg == 2) {
    /* control port: selin + feed + strobe (bits 3 + 1 + 0) */
    return(0x0B); }
  else {
    /* data port (or any other non existant value): no inverting */
    return(0); } }


/* input is written as: [1-9][0-9]* decimal, 0[0-7]* octal */
/*                      0b[01]* binary, 0x[0-9A-F]* hex, [^0-9] ascii */
unsigned char input(char *string) {

  unsigned long value;

  if (!string) {
    fprintf(stderr, "%s: ERROR: -w|-a|-o|-x no value given, aborting ...\n",
      MainProgName);
    exit(1); }

  /* binary special case because strtoul() known no 0b* */
  if      (string[0] == '0' && string[1] == 'b') {
    value = strtoul(string+2, &string, 2);
    if (*string != '\0') {
      fprintf(stderr, "%s: WARNING: not a value, at: '%s', ignoring rest\n",
        MainProgName, string); } }

  /* "0h" hex special case because strtoul() known no 0h* */
  /*   just in case someone uses it, in analogy to "h" in output format def */
  else if (string[0] == '0' && string[1] == 'h') {
    value = strtoul(string+2, &string, 16);
    if (*string != '\0') {
      fprintf(stderr, "%s: WARNING: not a value, at: '%s', ignoring rest\n",
        MainProgName, string); } }

  /* decimal, octal and "0x" hex */
  else if (isdigit(string[0])) {
    value = strtoul(string, &string, 0);
    if (*string != '\0') {
      fprintf(stderr, "%s: WARNING: not a value, at: '%s', ignoring rest\n",
        MainProgName, string); } }

  /* else ascii character, multiple formats possible */

  /* \<something> code for ascii character */
  else if (string[0] == '\\') {
    /* relatively senseless \<octal> code, in case a user does use this */
    if      (isdigit(string[1])) {
      value = strtoul(string+1, &string, 0); }
    /* the usual \<something> suspects */
    else if (string[1] == 'a') {
      value = 0x07; }
    else if (string[1] == 'b') {
      value = 0x08; }
    else if (string[1] == 't') {
      value = 0x09; }
    else if (string[1] == 'n') {
      value = 0x0A; }
    else if (string[1] == 'v') {
      value = 0x0B; }
    else if (string[1] == 'f') {
      value = 0x0C; }
    else if (string[1] == 'n') {
      value = 0x0D; }
    else if (string[1] == '\\') {
      value = 0x5C; } }

  else {
    /* direct ascii character */
    /*   note that this fails for characters 0-9, are mistaken for numbers */
    /*   should really be fixed, but with what syntax? */
    value = (unsigned long)string[0];
    if (string[1] != '\0') {
      fprintf(stderr, "%s: WARNING: multiple chars, at: '%s', ignoring\n",
        MainProgName, string+1); } }

  if (value > 0xff) {
    fprintf(stderr, "%s: WARNING: value too large: '%#08lx', truncating\n",
      MainProgName, value); }

  return((unsigned char)value); }


/* time in useconds */
unsigned long int utime(char *string) {

  unsigned long int utime;

  if (!string) {
    fprintf(stderr, "%s: ERROR: -u no time given, aborting ...\n",
      MainProgName);
    exit(1); }

  utime = strtoul(string, &string, 10);

  if (*string != '\0') {
    fprintf(stderr, "%s: WARNING: not time number, at: '%s', ignoring rest\n",
      MainProgName, string); }

  return(utime); }


/* --- format output --- */

/* get type for output formatting */
unsigned char type(char *string) {

  unsigned char type;

  if (!string) {
    fprintf(stderr, "%s: ERROR: -t no type given, aborting ...\n",
      MainProgName);
    exit(1); }

  type = string[0];

  if (string[1] != '\0') {
    fprintf(stderr, "%s: WARNING: multiple format chars, at: '%s', ignoring\n",
      MainProgName, string+1); }

  return(type); }


void output(unsigned char value) {
  char regs[] = "dsc";
  if      (MainVerbosity < 0) {
    /* only pure data */
    printf("%s\n", format(value)); }
  else if (MainVerbosity == 0) {
    /* port and data */
    printf("%#03hx.%c %s\n",
      MainPort, regs[MainReg], format(value)); }
  else {
    /* port and data, with labels */
    printf("port: %#03hx, register: %c, data: %s\n",
      MainPort, regs[MainReg], format(value)); } }


char Formatted[81];

/* formats are d decimal, u unsigned, o octal, b binary */
/*             h and x hex, a and c ascii */
char *format(unsigned char value) {

  if      (MainType == 'd' || MainType == 'D') {
    sprintf(Formatted, "%hhd", value); }

  else if (MainType == 'u' || MainType == 'U') {
    sprintf(Formatted, "%hhu", value); }

  else if (MainType == 'o' || MainType == 'O') {
    sprintf(Formatted, "0%hho", value); }

  else if (MainType == 'b' || MainType == 'B') {
    int bit;
    strcpy(Formatted, "0b");
    for (bit=7; bit>=0; bit--) {
      strcat(Formatted, ((value>>bit)&1) ? "1" : "0"); } }

  else if (MainType == 'h' || MainType == 'H' ||
           MainType == 'x' || MainType == 'X') {
    sprintf(Formatted, "0x%02hhx", value); }

  else if (MainType == 'a' || MainType == 'A' ||
           MainType == 'c' || MainType == 'C') {
    sprintf(Formatted, "'%hhc'", value); }

  else {

    fprintf(stderr, "%s: ERROR: unknown format: '%c', aborting ...\n",
      MainProgName, MainType);
    exit(1); }

  return(Formatted); }


/* --- output help text --- */

void helptext(FILE *Output) {

  fprintf(Output,
    "Usage: %s [-p port] [-dsc] [-waox value] [-r format] [-iqvh]\n",
    MainProgName);
  fprintf(Output,
    "\n");
  fprintf(Output,
    "Options:\n");
  fprintf(Output,
    "  -p port     port select: 0x378 (default) or 0x278 or 0x3BD\n");
  fprintf(Output,
    "  -d          data register (default): pins 9/8/7/6/5/4/3/2\n");
  fprintf(Output,
    "                8 pure data bits, output tristate by */outen\n");
  fprintf(Output,
    "  -s          status register: pins ^11|10|12|13|15|*|-|-\n");
  fprintf(Output,
    "                ^busy|/ack|paper-end|select|/error|*/irq|-|-\n");
  fprintf(Output,
    "                ^busy reads inverted as /busy\n");
  fprintf(Output,
    "                */irq only reads internal FF, no pin\n");
  fprintf(Output,
    "                this register is only readable, writing has no effect\n");
  fprintf(Output,
    "  -c          control register: pins -|-|*|*|^17|16|^14|^1\n");
  fprintf(Output,
    "                -|-|*/outen|*irqen|^/selin|/reset|^/feed|^/strobe\n");
  fprintf(Output,
    "                ^/selin ^/feed ^/strobe inverted as selin feed strobe\n");
  fprintf(Output,
    "                */outen and *irqen only set internal FFs, no pins\n");
  fprintf(Output,
    "                */outen can not be read, screws -a -o and -x options\n");
  fprintf(Output,
    "  -i          [pre-|un-]invert: correct inversion of '^' signals\n");
  fprintf(Output,
    "                write: precompensation invert, read: undo invert\n");
  fprintf(Output,
    "  -w value    write value to port register: replace all bits\n");
  fprintf(Output,
    "                values are: [1-9][0-9]* decimal, 0[0-7]* octal\n");
  fprintf(Output,
    "                            0b[0-1]* binary, 0x[0-9A-F]* hex\n");
  fprintf(Output,
    "                            [^0-9] ascii with \\code processing\n");
  fprintf(Output,
    "  -a value    write existing AND value to port: clear some bits\n");
  fprintf(Output,
    "  -o value    write existing OR value to port: set some bits\n");
  fprintf(Output,
    "  -x value    write existing XOR value to port: invert some bits\n");
  fprintf(Output,
    "  -u utime    usecond sleep: wait for utime microseconds\n");
  fprintf(Output,
    "  -t type     set output type: set format for output to type\n");
  fprintf(Output,
    "                types are: d decimal (default), u unsigned\n");
  fprintf(Output,
    "                           o octal, b binary, h and x hex\n");
  fprintf(Output,
    "                           a and c ascii/char\n");
  fprintf(Output,
    "  -r          read value from port register: output to stdout\n");
  fprintf(Output,
    "  -q          quiet operation: reduce verbosity, just raw data\n");
  fprintf(Output,
    "  -v          verbose operation: increase verbosity\n");
  fprintf(Output,
    "  -h          help: display this help text, then abort\n");
  fprintf(Output,
    "\n");
  fprintf(Output,
    "SECURITY WARNING: This program must run as root user or SIUD root\n");
  fprintf(Output,
    "  other SUID or SGID is not possible, it uses root-only system call\n");
  fprintf(Output,
    "  if SIUD root users can directly manipulate anything on any parport\n");
  fprintf(Output,
    "  do not use with any parport modules loaded, may crash kernel\n"); }

