/* kbd_cmd.c - aeb, 940505 */
/* 
 * This program must have write permission to /dev/port - probably that
 * means that it has to be suid root.
 *
 * Without arguments: enable keyboard.
 *
 * With a single argument "sane": enable keyboard and set default scancodes.
 *
 * [These are useful commands, but when the keyboard is in some obscure
 * state they probably cannot be typed. Under X you can have them as a
 * menu item. Otherwise you might use "sleep 300; kbd_cmd sane" before
 * doing something dangerous, like playing with this program.]
 *
 * With a sequence of two-character hexadecimal values or symbolic commands:
 *	send these values to the keyboard.
 * An argument W or w means wait.
 *
 * e.g., "kbd_cmd LED 7" will set the LEDs behind the kernel's back -
 *	use the program "setleds" if you want the lights to mean anything.
 *
 * "kbd_cmd f0 1; showkey -s; kbd_cmd f0 2" will enable you to study the
 *	scancodes produced in a different scancode mode.
 *
 * "kbd_cmd W f0 1 f0 0 W f0 2 & showkey -s" will enable you to find the
 *	identifications of the various scancode sets, and similarly
 * "kbd_cmd W f2 & showkey -s" yields the keyboard identification.
 *
 * Playing with these things is dangerous! It is very easy to get into a
 * state in which the keyboard cannot be used anymore. Set up an escape
 * (as suggested above).
 * These commands work on my keyboard. For other keyboards the results
 * may be unpredictible.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>

struct {
    unsigned char cmd;
    int argct, resct;
    char *name;
} commands[] = {
    0xed, 1, 0,	"LED",	  /* arg 0-7: 1 ScrollLock, 2 NumLock, 4 CapsLock */
    0xee, 0, 1,	"echo",		          /* result: ee */
    0xf0, 1, 1, "get_scancodes",          /* arg: 0, result: 43, 41 or 3f */
    0xf0, 1, 0,	"set_scancodes",          /* arg: 1-3 */
    0xf2, 0, 2,	"identify_keyboard",      /* result: ab 41 */
    0xf3, 1, 0,	"set_repeat_rate",
    0xf4, 0, 0,	"enable",
    0xf5, 0, 0,	"reset_and_disable",
    0xf6, 0, 0,	"reset_and_enable",
    0xfe, 0, 1,	"resend",
    0xff, 0, 1,	"reset_and_selftest"      /* result: aa (OK) / fc (error) */
};

int fd;
int args_expected;

send_cmd(unsigned char x) {
    char z;
    int i;

    do {
	lseek( fd, 0x64, 0 );
	read( fd, &z, 1 );
    } while ((z & 2) == 2 );  /* wait */

    lseek( fd, 0x60, 0 );
    write( fd, &x, 1 );

    if (args_expected)
      args_expected--;
    else {
	for(i=0; i<sizeof(commands)/sizeof(commands[0]); i++)
	  if(x == commands[i].cmd) {
	      args_expected = commands[i].argct;
	      break;
	  }
    }
}

int hexd(char c) {
    if ('0' <= c && c <= '9') return(c - '0');
    if ('a' <= c && c <= 'f') return(c - 'a' + 10);
    if ('A' <= c && c <= 'F') return(c - 'A' + 10);
    fprintf(stderr, "kbd_cmd: expected a hex digit, got _%c_ (0%o)\n", c);
    leave(1);
}

unsigned char tohex(char *s) {
    if(!s[0])
      return(0);
    else if(!s[1])
      return(hexd(s[0]));
    else
      return((hexd(s[0])<<4) + hexd(s[1]));
}

void main(int argc, char **argv) {
   int i, j;

   if ( (fd = open("/dev/port", O_RDWR)) < 0) {
      perror("Cannot open /dev/port");
      exit(1);
   }

   if (argc < 2) {
       send_cmd(0xf4);		/* enable */
   } else if (argc == 2 && !strcmp(argv[1], "sane")) {
       send_cmd(0);		/* Just in case the kbd was waiting */
				/* for the second byte of a command */
       send_cmd(0xf0);		/* Select scancode set */
       send_cmd(0x02);		/* set 2 */
       send_cmd(0xf4);		/* Enable keyboard */
   } else for (i=1; i<argc; i++) {
       if (!strcmp(argv[i], "W") || !strcmp(argv[i], "w")) {
	   sleep(1);
       } else if (strlen(argv[i]) > 2) {
	   for (j=0; j < sizeof(commands)/sizeof(commands[0]); j++)
	     if(!strcmp(commands[j].name, argv[i])) {
		 send_cmd(commands[j].cmd);
		 goto fnd;
	     }
	   fprintf(stderr, "kbd_cmd: unrecognized command %s\n", argv[i]);
	   leave(1);
	 fnd:;
       } else
	 send_cmd(tohex(argv[i]));
   }
   leave(0);
}

leave(int n) {
    /* prevent frozen keyboards, waiting for command arguments */
    while(args_expected)
      send_cmd(0);
    exit(n);
}



