/* 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);
}