Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

arch_kbd.c

/* $Id: arch_kbd.c,v 1.29 2009-01-28 12:59:16 potyra Exp $ 
 *
 * Copyright (C) 2006-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#if defined(STATE)

struct {
      int n_reset_state;
      int a20gate_state;
      unsigned char modereg;

      unsigned char led_status;
      enum {
            KEYBOARD,
            MOUSE,
            OUT_PORT,
            WRITE_MODE,
            AUX_OBUF,
            /* Controller input buffer */
            IN_BUF, 
      } redir;

#define KBD_BUFSIZE     256
      volatile struct {
            unsigned char data[KBD_BUFSIZE];
            int count;
            int head;
            int tail;
      } buf[2];   /* Buffer 0: kbd, 1: mouse */
      
      volatile unsigned char statusreg;
      volatile unsigned char lastdata;

      volatile unsigned char lastwrittento; /* Which port was last written
                                     * to - used in the status reg */

      enum {
            AT,
            MCA   /* PS/2 */
      } ctrl_mode;
} NAME;

#elif defined(BEHAVIOR)

#define DEBUG_CONTROL_FLOW    0
#define DEBUG_IRQ       0

/*
 * Status Register Bits
 */
#define KBD_STAT_PERR         0x80 /* Parity error */
#define KBD_STAT_GTO          0x40 /* General receive/xmit timeout */
#define KBD_STAT_MOUSE_OBF    0x20 /* Mouse output buffer full */
#define KBD_STAT_UNLOCKED     0x10 /* Zero if keyboard locked */
#define KBD_STAT_CMD          0x08 /* Last write was a command write (0=data) */
#define KBD_STAT_SELFTEST     0x04 /* Self test successful */
#define KBD_STAT_IBF          0x02 /* Keyboard input buffer full */
#define KBD_STAT_OBF          0x01 /* Keyboard output buffer full */

/*
 * Control register bits
 */
#define KBD_CONTROL_KBDINT    0x01
#define KBD_CONTROL_AUXINT    0x02
#define KBD_CONTROL_IGNKEYLOCK      0x08
#define KBD_CONTROL_KBDDIS    0x10
#define KBD_CONTROL_AUXDIS    0x20
#define KBD_CONTROL_XLATE     0x40

/* Keyboard Controller Commands */
/* see PC-Hardware 5th revision, pp. 1034 */
#define KBD_CCMD_READ_MODE    0x20 /* Read mode bits */
#define KBD_CCMD_WRITE_MODE   0x60 /* Write mode bits */
#define KBD_CCMD_GET_VERSION  0xA1 /* Get controller version */
#define KBD_CCMD_RSV1         0xA4 /* reserved */
#define KBD_CCMD_RSV2         0xA5 /* reserved */
#define KBD_CCMD_RSV3         0xA6 /* reserved */
#define KBD_CCMD_MOUSE_DISABLE      0xA7 /* Disable mouse interface */
#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */
#define KBD_CCMD_TEST_MOUSE   0xA9 /* Mouse interface test */
#define KBD_CCMD_SELF_TEST    0xAA /* Controller self test */
#define KBD_CCMD_KBD_TEST     0xAB /* Keyboard interface test */
#define KBD_CCMD_KBD_DISABLE  0xAD /* Keyboard interface disable */
#define KBD_CCMD_KBD_ENABLE   0xAE /* Keyboard interface enable */
#define KBD_CCMD_RESET_CLINE  0xB0 /* reset controller line, bits 2-0 select line */
#define KBD_CCMD_SET_CLINE    0xB8 /* set controller line, bits 2-0 select line  */
#define KBD_CCMD_RDIN         0xC0 /* read input port */
#define KBD_CCMD_PLINH        0xC2 /* poll input port high */
#define KBD_CCMD_PLINL        0xC3 /* poll input port low */
#define KBD_CCMD_READ_CTRL_MODE     0xCA /* Read keyboard controller mode */
#define KBD_CCMD_RDOUT        0xD0 /* read output port */
#define KBD_CCMD_WROUT        0xD1 /* write output port */
#define KBD_CCMD_WRKOR        0xD2 /* write keyboard output register */
#define KBD_CCMD_WRITE_AUX_OBUF     0xD3 /* write auxiliary device output register*/
#define KBD_CCMD_WRITE_MOUSE  0xD4 /* write the following byte to the mouse */
#define KBD_CCMD_RDTST        0xE0 /* read test input port */
#define KBD_CCMD_PLSE         0xF0 /* pulse: bits 3-0 select which bit */


static void
NAME_(recalculate_status_reg)(struct cpssp *cpssp)
{
      unsigned char value;
      
      value = 0;

      /* parity error? */
      /* value |= KBD_STAT_PERR; */

      /* timing error? */
      /* value |= KBD_STAT_GTO; */

      /*
       * First look for data in keyboard buffer.
       * After that look for data in mouse buffer.
       * Otherwise OpenBSD will get wrong data if mouse is moved.
       */
      /* Keyboard buffer full? */
      if (1 <= cpssp->NAME.buf[0].count) {
            value |= KBD_STAT_OBF;
      } else if (1 <= cpssp->NAME.buf[1].count) {
            /* no Keyboard but at least mouse data available */
            value |= KBD_STAT_OBF;
            value |= KBD_STAT_MOUSE_OBF;
      }

      /* keyboard unlocked? */
      value |= KBD_STAT_UNLOCKED;

      /* command or data */
      if (cpssp->NAME.lastwrittento) {
            value |= KBD_STAT_CMD;
      }

      /* system flag FIXME VOSSI */
      value |= KBD_STAT_SELFTEST; /* We've already passed selftest */

      /* data in input buffer? Can never happen in current impl. */
      /* value |= KBD_STAT_IBF; */

      cpssp->NAME.statusreg = value;
}

static void
NAME_(push)(struct cpssp *cpssp, unsigned int buf, unsigned char val)
{
      if (KBD_BUFSIZE <= cpssp->NAME.buf[buf].count) {
            faum_log(FAUM_LOG_WARNING, SNAME,
                   (buf ? "mouse" : "keyboard"),
                   "Keyboard controller: %s buffer overflow\n",
                   (buf ? "mouse" : "keyboard"));
            return;
      }

      cpssp->NAME.buf[buf].data[cpssp->NAME.buf[buf].head] = val;
      cpssp->NAME.buf[buf].count++;
      cpssp->NAME.buf[buf].head = (cpssp->NAME.buf[buf].head + 1) % KBD_BUFSIZE;

      if (! (cpssp->NAME.statusreg & KBD_STAT_OBF)) {
            NAME_(recalculate_status_reg)(cpssp);
            if (cpssp->NAME.statusreg & KBD_STAT_MOUSE_OBF) {
                  /* Signal interrupt. */
                  if (cpssp->NAME.modereg & KBD_CONTROL_AUXINT) {
                        NAME_(intC_set)(cpssp, 1);
                  }
            } else if (cpssp->NAME.statusreg & KBD_STAT_OBF) {
                  /* Signal interrupt. */
                  if (cpssp->NAME.modereg & KBD_CONTROL_KBDINT) {
                        NAME_(int1_set)(cpssp, 1);
                  }
            }
      }
}

static unsigned char
NAME_(pop)(struct cpssp *cpssp, unsigned int buf)
{
      unsigned char value;

      assert(0 < cpssp->NAME.buf[buf].count);

      /* disable keyboard interrupt while updating cpssp->NAME.count! FIXME */
      value = cpssp->NAME.buf[buf].data[cpssp->NAME.buf[buf].tail];
      cpssp->NAME.buf[buf].count--;
      cpssp->NAME.buf[buf].tail = (cpssp->NAME.buf[buf].tail + 1) % KBD_BUFSIZE;

      /*
       * Reset last interrupt.
       */
      if (cpssp->NAME.statusreg & KBD_STAT_MOUSE_OBF) {
            NAME_(intC_set)(cpssp, 0);
      } else if (cpssp->NAME.statusreg & KBD_STAT_OBF) {
            NAME_(int1_set)(cpssp, 0);
      }

      NAME_(recalculate_status_reg)(cpssp);

      /*
       * Signal new interrupt if necessary.
       */
      if (cpssp->NAME.statusreg & KBD_STAT_MOUSE_OBF) {
            if (cpssp->NAME.modereg & KBD_CONTROL_AUXINT) {
                  NAME_(intC_set)(cpssp, 1);
            }
      } else if (cpssp->NAME.statusreg & KBD_STAT_OBF) {
            if (cpssp->NAME.modereg & KBD_CONTROL_KBDINT) {
                  NAME_(int1_set)(cpssp, 1);
            }
      }
      
      return value;
}

static void
NAME_(do_keyboard_command)(struct cpssp *cpssp, unsigned char value)
{
      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: value=0x%02x\n", __FUNCTION__,
                        time_virt(), value);
      }

      /* Any keyboard command enables the keyboard again. */
      cpssp->NAME.modereg &= ~KBD_CONTROL_KBDDIS;

      NAME_(kbd_send)(cpssp, value);

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: return\n", __FUNCTION__,
                        time_virt());
      }
}

/*
 * See PC Hardware (5. Auflage), S. 1048.
 */
static void
NAME_(do_mouse_command)(struct cpssp *cpssp, unsigned char value)
{
      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: value=0x%02x\n", __FUNCTION__,
                        time_virt(), value);
      }

      /* Unlike with the keyboard, a mouse command does not reenable the
       * mouse automatically */

      NAME_(mouse_send)(cpssp, value);

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: return\n", __FUNCTION__,
                        time_virt());
      }
}

static void
NAME_(do_controller_command)(struct cpssp *cpssp, unsigned char value)
{
      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: value=0x%02x\n", __FUNCTION__,
                        time_virt(), value);
      }

      /*
       * Commands used for keyboard initialization in initialize_keyboard()
       * in drivers/char/pc_keyb.c
       */
      switch (value) {
      case KBD_CCMD_SELF_TEST:      /* 0xAA: Controller Self Test */
            /*
             * Bit 0 of the controller status register is set to 1 upon
             * completion of self test. Linux driver doesn't check this.
             */
            NAME_(push)(cpssp, 0, 0x55); /* "All OK" */
            break;

      case KBD_CCMD_KBD_TEST:       /* 0xAB: Keyboard Interface Test */
            NAME_(push)(cpssp, 0, 0x00); /* "All OK" */
            break;

      case KBD_CCMD_TEST_MOUSE:     /* 0xA9: Mouse Interface Test */
            NAME_(push)(cpssp, 0, 0x00); /* "All OK" */
            break;

      case KBD_CCMD_KBD_DISABLE:    /* 0xAD: Disable Keyboard */
            cpssp->NAME.modereg |= KBD_CONTROL_KBDDIS;
            break;

      case KBD_CCMD_KBD_ENABLE:     /* 0xAE: Enable Keyboard */
            cpssp->NAME.modereg &= ~KBD_CONTROL_KBDDIS;
            break;

      case KBD_CCMD_WRITE_MODE:     /* 0x60: write mode register */
            cpssp->NAME.redir = WRITE_MODE;
            break;

      case KBD_CCMD_READ_MODE:      /* 0xCA: read mode register */
            NAME_(push)(cpssp, 0, cpssp->NAME.modereg);
            break;

      case KBD_CCMD_WRITE_AUX_OBUF: /* 0xD3: write to output buffer as if */
                              /* initiated by the auxiliary device */
            cpssp->NAME.redir = AUX_OBUF;
            break;

      case KBD_CCMD_MOUSE_DISABLE:  /* 0xA7: disable mouse */
            cpssp->NAME.modereg |= KBD_CONTROL_AUXDIS;
            break;

      case KBD_CCMD_MOUSE_ENABLE:   /* 0xA8: enable mouse */
            cpssp->NAME.modereg &= ~KBD_CONTROL_AUXDIS;
            break;

      case KBD_CCMD_RESET_CLINE ... (KBD_CCMD_RESET_CLINE | 0x07):
                              /* 0xB0 ... 0xB7 write 0 to controller lines */
                              /* order is: I0, I1, I2, I3, O2, O3, I4, I5 */

            /* FIXME _really_ reset lines here */
            faum_log(FAUM_LOG_WARNING, SNAME, "",
                        "Trying to reset controller port: 0x%02x\n",
                        value);

            NAME_(push)(cpssp, 0, 0); /* (one byte of garbage) */
            break;

      case KBD_CCMD_SET_CLINE ... (KBD_CCMD_SET_CLINE | 0x07):
                              /* 0xB8 ... 0xBF write 1 to controller lines */
                              /* (order: see above) */

            /* FIXME _really_ set lines here */
            faum_log(FAUM_LOG_WARNING, SNAME, "",
                        "Trying to set controller port: 0x%02x\n",
                        value);

            NAME_(push)(cpssp, 0, 0); /* (one byte of garbage) */
            break;

      case KBD_CCMD_RDOUT: {        /* 0xD0: read output port */
            unsigned char val;

            val = 0;
            val |= cpssp->NAME.n_reset_state << 0;          /* system reset pin */
            val |= cpssp->NAME.a20gate_state << 1;          /* A20 gate */
            val |= ((cpssp->NAME.statusreg & 1) >> 0) << 4; /* ouput buffer full */
            val |= (cpssp->NAME.statusreg & 2) ? 0 : (1 << 5);/* input buffer empty */

            NAME_(push)(cpssp, 0, val);
            break;
            }
      case KBD_CCMD_WROUT:          /* 0xD1: write output port */
            cpssp->NAME.redir = OUT_PORT;
            break;

      case KBD_CCMD_WRKOR:          /* 0xD2: Write keyboard buffer */
            cpssp->NAME.redir = IN_BUF;
            break;

      case KBD_CCMD_WRITE_MOUSE:    /* 0xD4 - write to aux device FIXME */
            cpssp->NAME.redir = MOUSE;
            break;

      case KBD_CCMD_PLSE | 0x0e:    /* 0xFE: pulse reset line */
            cpssp->NAME.n_reset_state = 0;
            NAME_(n_reset_set)(cpssp, 0);
            cpssp->NAME.n_reset_state = 1;
            NAME_(n_reset_set)(cpssp, 1);
            break;

      case KBD_CCMD_RDIN: {         /* 0xC0: Read input register. */
            unsigned char val;

            /* FIXME VOSSI */
            val = 0;
            val |= 1 << 7;    /* Keyboard not locked. */
            val |= 1 << 5;    /* No manufacturer POST loop. */

            NAME_(push)(cpssp, 0, val);
            break;
          }
      case KBD_CCMD_READ_CTRL_MODE: /* 0xCA Read controller mode */
            NAME_(push)(cpssp, 0, cpssp->NAME.ctrl_mode);
            break;

      case KBD_CCMD_PLSE | 0x0f:    /* 0xFF: possibly sortof a "reset" */
            /* Ignore that - I don't know what is its use. */
            /* Ignored by QEMU as well. Seems to be OK to ignore it. */
            /* "the internet" says it generates a short pulse on all the
             * controllers output lines? - probably some sort of reset */
            break;
      
      case KBD_CCMD_PLINH: /* Other commands: */
      case KBD_CCMD_PLINL:
      case KBD_CCMD_RDTST:
      case KBD_CCMD_PLSE | 0x00:
      case KBD_CCMD_PLSE | 0x01:
      case KBD_CCMD_PLSE | 0x02:
      case KBD_CCMD_PLSE | 0x03:
      case KBD_CCMD_PLSE | 0x04:
      case KBD_CCMD_PLSE | 0x05:
      case KBD_CCMD_PLSE | 0x06:
      case KBD_CCMD_PLSE | 0x07:
      case KBD_CCMD_PLSE | 0x08:
      case KBD_CCMD_PLSE | 0x09:
      case KBD_CCMD_PLSE | 0x0a:
      case KBD_CCMD_PLSE | 0x0b:
      case KBD_CCMD_PLSE | 0x0c:
      case KBD_CCMD_PLSE | 0x0d:
      /* case KBD_CCMD_PLSE | 0x0e: see above */
      /* case KBD_CCMD_PLSE | 0x0f: see above */
            /* a7 */
            faum_log(FAUM_LOG_WARNING, SNAME, "",
                   "Unimplemented keyboard controller command: 0x%02x\n",
                   value);
            break;

      default:
            faum_log(FAUM_LOG_WARNING, SNAME, "",
                   "Unimplemented keyboard controller command: 0x%02x\n",
                   value);
            break;
      }

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: return\n", __FUNCTION__,
                        time_virt());
      }
}

static uint8_t
NAME_(inb)(struct cpssp *cpssp, unsigned short addr)
{
      uint8_t value;

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: addr=0x%02x\n", __FUNCTION__,
                        time_virt(), addr);
      }

      switch (addr) {
      case 0:
            if (cpssp->NAME.statusreg & KBD_STAT_MOUSE_OBF) {
                  cpssp->NAME.lastdata = NAME_(pop)(cpssp, 1);
            } else if (cpssp->NAME.statusreg & KBD_STAT_OBF) {
                  cpssp->NAME.lastdata = NAME_(pop)(cpssp, 0);
            }
            value = cpssp->NAME.lastdata;
            break;

      case 1:
            value = cpssp->NAME.statusreg;
            break;

      default:
            assert(0);
      }

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: value=0x%02x\n", __FUNCTION__,
                        time_virt(), value);
      }

      return value;
}

static void
NAME_(outb)(struct cpssp *cpssp, unsigned char value, unsigned short addr)
{
      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: addr=0x%02x, value=0x%02x\n",
                        __FUNCTION__, time_virt(), addr, value);
      }

      switch (addr) {
      case 0:
            cpssp->NAME.lastwrittento = 0;
            switch (cpssp->NAME.redir) {
            case WRITE_MODE:
                  /* enable/disable interrupts */
                  if (cpssp->NAME.statusreg & KBD_STAT_MOUSE_OBF) {
                        if ((cpssp->NAME.modereg & KBD_CONTROL_AUXINT)
                         != (value & KBD_CONTROL_AUXINT)) {
                              NAME_(intC_set)(cpssp, (value & KBD_CONTROL_AUXINT) ? 1 : 0);
                        }
                  } else if (cpssp->NAME.statusreg & KBD_STAT_OBF) {
                        if ((cpssp->NAME.modereg & KBD_CONTROL_KBDINT)
                         != (value & KBD_CONTROL_KBDINT)) {
                              NAME_(int1_set)(cpssp, (value & KBD_CONTROL_KBDINT) ? 1 : 0);
                        }
                  }
                  cpssp->NAME.modereg = value;
                  break;

            case OUT_PORT:
                  cpssp->NAME.a20gate_state = (value >> 1) & 1;
                  NAME_(a20gate_set)(cpssp, cpssp->NAME.a20gate_state);
                  break;

            case AUX_OBUF:
                  NAME_(push)(cpssp, 1, value);
                  break;

            case KEYBOARD:
                  NAME_(do_keyboard_command)(cpssp, value);
                  break;

            case MOUSE:
                  NAME_(do_mouse_command)(cpssp, value);
                  break;

            case IN_BUF:
                  NAME_(push)(cpssp, 0, value);
                  break;

            default:
                  assert(0);
            }
            cpssp->NAME.redir = KEYBOARD;
            break;

      case 1:
            cpssp->NAME.lastwrittento = 1;
            NAME_(do_controller_command)(cpssp, value);
            break;

      default:
            assert(0);
      }

      if (DEBUG_CONTROL_FLOW) {
            fprintf(stderr, "%s: %llu: return\n",
                        __FUNCTION__, time_virt());
      }
}

static void
NAME_(kbd_recv)(struct cpssp *cpssp, uint8_t byte)
{
#if DEBUG_IRQ
      faum_log(FAUM_LOG_DEBUG, SNAME, "keyboard",
             "byte for keyboard: %02x\n", byte);
#endif
      
      if (! (cpssp->NAME.modereg & KBD_CONTROL_KBDDIS)) {
            NAME_(push)(cpssp, 0, byte);
      } else {
            faum_log(FAUM_LOG_INFO, SNAME, "keyboard",
                   "Discarding Keyboard data "
                   "(KBD disabled on keyboard controller)\n");
      }
}

static void
NAME_(mouse_recv)(struct cpssp *cpssp, uint8_t byte)
{
#if DEBUG_IRQ
      faum_log(FAUM_LOG_DEBUG, SNAME, "mouse",
             "byte for mouse: %02x\n", byte);
#endif

      if (! (cpssp->NAME.modereg & KBD_CONTROL_AUXDIS)) {
            NAME_(push)(cpssp, 1, byte);
#if 0
      } else {
            faum_log(FAUM_LOG_INFO, SNAME, "mouse",
                   "Discarding Mouse data "
                   "(AUX disabled on keyboard controller)\n");
#endif
      }
}

static void
NAME_(reset)(struct cpssp *cpssp)
{
      cpssp->NAME.ctrl_mode = MCA;
}

#undef DEBUG_IRQ
#undef DEBUG_CONTROL_FLOW

#endif /* defined(BEHAVIOR) */

Generated by  Doxygen 1.6.0   Back to index