]> xenbits.xen.org Git - xenclient/linux-2.6.27-pq.git/commitdiff
Kernel-side driver (pass2) for PS/2 pass-through for XenClient.
authorMark Hemment <markhem@isildur.uk.xensource.com>
Fri, 29 May 2009 13:41:27 +0000 (14:41 +0100)
committerMark Hemment <markhem@isildur.uk.xensource.com>
Fri, 29 May 2009 13:41:27 +0000 (14:41 +0100)
master/pass2-driver [new file with mode: 0644]

diff --git a/master/pass2-driver b/master/pass2-driver
new file mode 100644 (file)
index 0000000..85359df
--- /dev/null
@@ -0,0 +1,1228 @@
+diff --git a/drivers/input/Makefile b/drivers/input/Makefile
+index 98c4f9a..414082b 100644
+--- a/drivers/input/Makefile
++++ b/drivers/input/Makefile
+@@ -21,6 +21,7 @@ obj-$(CONFIG_INPUT_JOYSTICK) += joystick/
+ obj-$(CONFIG_INPUT_TABLET)    += tablet/
+ obj-$(CONFIG_INPUT_TOUCHSCREEN)       += touchscreen/
+ obj-$(CONFIG_INPUT_MISC)      += misc/
++obj-$(CONFIG_INPUT_XEN)               += xen/
+ obj-$(CONFIG_INPUT_APMPOWER)  += apm-power.o
+diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
+index 27d70d3..b2f4ff7 100644
+--- a/drivers/input/serio/Kconfig
++++ b/drivers/input/serio/Kconfig
+@@ -19,7 +19,7 @@ config SERIO
+ if SERIO
+ config SERIO_I8042
+-      tristate "i8042 PC Keyboard controller" if EMBEDDED || !X86
++      tristate "i8042 PC Keyboard controller" if EMBEDDED || X86
+       default y
+       depends on !PARISC && (!ARM || ARCH_SHARK || FOOTBRIDGE_HOST) && !M68K && !BLACKFIN
+       ---help---
+diff --git a/drivers/input/xen/Kconfig b/drivers/input/xen/Kconfig
+new file mode 100644
+index 0000000..c45f063
+--- /dev/null
++++ b/drivers/input/xen/Kconfig
+@@ -0,0 +1,25 @@
++#
++# Mouse driver configuration
++#
++menuconfig INPUT_XEN
++      bool "XEN"
++      default n
++      help
++        Say Y here for input devices specific to Xen.
++        This option doesn't affect the kernel.
++
++        If unsure, say N.
++
++if INPUT_XEN
++
++config XEN_8042
++      tristate "8042 KBD and Mouse"
++      default y
++      ---help---
++        Say Y here if you have a PS/2 keyboard and/or mouse connected to
++        your system that you wish to virtualize between Xen Client guests.
++        If unsure, say Y.
++
++        To compile this driver as a module, choose M here: the
++        module will be called pass2 (for pass-through PS/2).
++endif
+diff --git a/drivers/input/xen/Makefile b/drivers/input/xen/Makefile
+new file mode 100644
+index 0000000..25e4bce
+--- /dev/null
++++ b/drivers/input/xen/Makefile
+@@ -0,0 +1,7 @@
++#
++# Makefile for Xen Client input devices.
++#
++
++obj-$(CONFIG_XEN_8042)                += pass2.o
++
++xeninput-objs  := pass2.o
+diff --git a/drivers/input/xen/pass2.c b/drivers/input/xen/pass2.c
+new file mode 100644
+index 0000000..550b07f
+--- /dev/null
++++ b/drivers/input/xen/pass2.c
+@@ -0,0 +1,1153 @@
++/*
++ * Kernel-side driver of XenClient's pass-through PS/2 solution.
++ * Most the intelligence is in ioemu.  This driver talks to the h/w (i8042),
++ * handles interrupts, arbitrates on which guest has focus on the devices
++ * (KBD and AUX), and passes constant data from the primary VM to the
++ * secondaries.
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++#include <linux/delay.h>
++#include <linux/interrupt.h>
++#include <linux/ioport.h>
++#include <linux/errno.h>
++#include <linux/fs.h>
++#include <linux/proc_fs.h>
++#include <linux/errno.h>
++#include <linux/proc_fs.h>
++#include <linux/stat.h>
++#include <linux/poll.h>
++#include <linux/pass2.h>
++#include <linux/irq.h>
++#include <linux/init.h>
++#include <linux/spinlock.h>
++#include <linux/cpu.h>
++
++#include <asm/io.h>
++
++/**
++ ** Compile control.
++ **/
++
++/* #define    PASS2_DEBUG     1 */
++
++/*
++ * Constants.
++ */
++
++#define       PA2_KBD_IRQ     1
++#define       PA2_AUX_IRQ     12
++
++#define       PA2_DATA_PORT           0x60
++#define       PA2_CMD_PORT            0x64
++#define       PA2_STATUS_PORT         0x64
++
++/*
++ * Status Register bits.
++ */
++
++#define PA2_CTL_STAT_OBF              0x01    /* KBD output buffer full */
++#define PA2_CTL_STAT_IBF              0x02    /* KBD input buffer full */
++#define PA2_CTL_STAT_SELFTEST         0x04    /* Self test successful */
++#define PA2_STAT_CMD                  0x08    /* Set when last write was a
++                                               * cmd */
++#define PA2_CTL_STAT_UNLOCKED         0x10    /* Zero if keyboard locked */
++#define PA2_CTL_STAT_AUX_OBF          0x20    /* AUX output buffer full */
++#define PA2_CTL_STAT_GTO              0x40    /* receive/xmit timeout */
++#define PA2_CTL_STAT_PERR             0x80    /* Parity error */
++
++/*
++ * Controller Mode Register Bits.
++ */
++
++#define PA2_CTL_MODE_INT_KBD          0x01    /* KBD data generate IRQ1 */
++#define PA2_CTL_MODE_INT_AUX          0x02    /* AUX data generate IRQ12 */
++#define PA2_CTL_MODE_SYS              0x04    /* The system flag (?) */
++#define PA2_CTL_MODE_NO_KEYLOCK               0x08    /* keylock doesn't affect the
++                                               * KBD */
++#define PA2_CTL_MODE_DISABLE_KBD      0x10    /* Disable keyboard interface */
++#define PA2_CTL_MODE_DISABLE_AUX      0x20    /* Disable aux interface */
++#define PA2_CTL_MODE_KCC              0x40    /* Scan code conversion to PC
++                                               * format */
++#define PA2_CTL_MODE_RFU              0x80
++
++/*
++ * Keyboard Controller Commands
++ */
++
++#define       PA2_CTL_CMD_MODE_READ           0x20    /* read mode bits */
++#define       PA2_CTL_CMD_MODE_WRITE          0x60    /* write mode bits */
++
++/**
++ ** Debug
++ **/
++
++#if   defined(PASS2_DEBUG)
++#define       ASSERT(cond)                    \
++      do {                                                            \
++              if ((cond) != 0) {                                      \
++                      break;                                          \
++              }                                                       \
++              printk(KERN_ERR "pass2: BUG on line:%d \"%s\"\n",       \
++                              __LINE__, #cond);                       \
++              panic("ASSERT\n");                                      \
++      } while (0)
++
++#define       PRINT_DEBUG(format, args...)    \
++      do {                                                            \
++              printk(KERN_ERR "pass2: " format, ## args);             \
++      } while (0)
++#else
++#define       ASSERT(cond)                    do { } while (0)
++#define       PRINT_DEBUG(format, args...)    do { } while (0)
++#endif
++
++/*
++ * Names of pseudo files.
++ */
++
++static const char     *pa2_path[] = { "8042-kbd", "8042-aux", "8042-ctl" };
++
++#define       PA2_INDEX_KBD   0
++#define       PA2_INDEX_AUX   1
++#define       PA2_INDEX_CTL   2
++
++/**
++ ** Queues.
++ ** Data is stored on two queues, one for KDB and one for AUX, until read
++ ** by the user-space driver.
++ ** The AUX device, when it is a Synaptic touchpad, can generate up to 80
++ ** sample packets per second with each packet being 6 bytes (480 bytes per
++ ** second).  Queue is sized to store just over a seconds worth (512 entries).
++ **/
++
++#define       PA2_BUF_SHIFT   9
++#define       PA2_BUF_SZ      (1 << PA2_BUF_SHIFT)
++
++/*
++ * Received data queue structure;
++ *  pp_entry          - data/status
++ *  pp_start          - first unused position in pp_entry
++ *  pp_end            - oldest, unread, data in pp_entry
++ *  pp_overflow               - an overflow occured since last user-space read
++ */
++
++typedef struct pa2_ps_s {
++      pa2_entry_t     pp_entry[PA2_BUF_SZ];
++      unsigned int    pp_start;
++      unsigned int    pp_end;
++      unsigned int    pp_overflow;
++} pa2_ps_t;
++
++/*
++ * The state of each device.  There are two of these, one for KBD other for
++ * AUX.
++ * Only the file structure which has grabbed (PA2_IOCTL_GRAB) the device can
++ * read data from it (this should be extended to sending cmds).
++ *  ps_lock           - guard for data on device's queue (ps_queue), and
++ *                      for grabbed status (ps_grabbed).
++ *  ps_index          - PA2_INDEX_{AUX,KBD}
++ *  ps_waiting_grab   - XXX not fully implemented....
++ *  ps_grabbed                - pointer to file structure that owns (grabbed) the
++ *                      device (PA2_IOCTL_GRAB).
++ *  ps_queue          - pointer to received data queue
++ *  ps_wait_grab      - XXX not fully implemented...
++ *  ps_wait_poll      - waiting for incoming data..XXX
++ */
++
++typedef struct pa2_state_s {
++      spinlock_t              ps_lock;
++      unsigned int            ps_index;
++      unsigned int            ps_waiting_grab;
++      struct file             *ps_grabbed;
++      pa2_ps_t                *ps_queue;
++      wait_queue_head_t       ps_wait_grab;
++      wait_queue_head_t       ps_wait_poll;
++} pa2_state_t;
++
++/*
++ * For the ctl file.
++ * The members must match the start of pa2_state_t.
++ */
++
++typedef struct pa2_ctl_s {
++      spinlock_t              ps_lock;
++      unsigned int            ps_index;
++} pa2_ctl_t;
++
++/**
++ ** Globals
++ **/
++
++static pa2_ps_t               pa2_queue[PA2_INDEX_AUX + 1];
++static pa2_state_t    pa2_state[PA2_INDEX_CTL + 1];   /* surely AUX? XXX */
++static pa2_ctl_t      pa2_ctl;
++
++/*
++ *  pa2_i8042_lock    - guards reading from the data port, and some globals
++ *                      (pa2_open_count, XXX)
++ *  pa2_open_count    - number of file opens
++ *  pa2_initialized   - flag indicating if one time initialization has
++ *                      been performed by user-space
++ *  pa2_init_taskp    - task performing the initialization
++ *  pa2_init_buffer   - copy of initialization data
++ *  pa2_init_buffer_tmp       - temp copy of initialization data, copied from user-
++ *                      space.  Not taken on stack due to size.
++ *  pa2_proc_dev      - /proc files
++ *  pa2_init_sem      - semaphore guard held during initilization
++ *  pa2_cmd_sem               - controls access to cmd register
++ *  pa2_cmd_filp      - file structure that holds the cmd_sem
++ */
++
++static        DEFINE_SPINLOCK(pa2_i8042_lock);        /* cache aligned XXX */
++static unsigned int   pa2_open_count;
++static int            pa2_initialized;
++static struct task_struct     *pa2_init_taskp;
++static pa2_data_init_t                pa2_init_buffer;
++static pa2_data_init_t                pa2_init_buffer_tmp;
++static struct proc_dir_entry  *pa2_proc_dev[PA2_INDEX_CTL + 1];
++
++static        DECLARE_MUTEX(pa2_init_sem);
++static        DECLARE_MUTEX(pa2_cmd_sem);
++static struct file    *pa2_cmd_filp;
++
++/*
++ * Functions for accessing status, data, and cmd ports.
++ */
++
++static inline u8
++pa2_status_read(
++      void)
++{
++      return inb(PA2_STATUS_PORT);
++}
++
++static inline u8
++pa2_data_read(
++      void)
++{
++      return inb(PA2_DATA_PORT);
++}
++
++static inline void
++pa2_cmd_write(
++      u8      byte)
++{
++      outb(byte, PA2_CMD_PORT);
++      return;
++}
++
++/**
++ ** Support functions.
++ **/
++
++static __attribute__ ((format (printf, 1, 2))) void
++pa2_error(
++      const char      *fmt,
++      ...)
++{
++      va_list         ap;
++
++      printk(KERN_ERR "pa2: ");
++      va_start(ap, fmt);
++      vprintk(fmt, ap);
++      va_end(ap);
++      return;
++}
++
++/*
++ * Wait for input buffer to become empty.
++ */
++
++static int
++pa2_wait_on_input_buffer(
++      void)
++{
++      int     i;
++      int     ret;
++      u8      status;
++
++      ret = 1;
++      for (i = 0; i < 10000; i++) {
++              status = pa2_status_read();
++              if (status & PA2_CTL_STAT_IBF) {
++                      udelay(50);
++                      continue;
++              }
++              ret = 0;
++              break;
++      }
++      return ret;
++}
++
++static int
++pa2_wait_on_output_buf(
++      void)
++{
++      int     i;
++      int     ret;
++      u8      status;
++
++      ret = 1;
++      for (i = 0; i < 10000; i++) {
++              status = pa2_status_read();
++              if (!(status & PA2_CTL_STAT_OBF)) {
++                      udelay(50);
++                      continue;
++              }
++              ret = 0;
++              break;
++      }
++      return ret;
++}
++
++static int
++pa2_wait_on_aux_output_buf(
++      void)
++{
++      int     i;
++      int     ret;
++      u8      status;
++
++      ret = 1;
++      for (i = 0; i < 10000; i++) {
++              status = pa2_status_read();
++              if ((status & (PA2_CTL_STAT_AUX_OBF | PA2_CTL_STAT_OBF)) !=
++                            (PA2_CTL_STAT_AUX_OBF | PA2_CTL_STAT_OBF)) {
++                      udelay(50);
++                      continue;
++              }
++              ret = 0;
++              break;
++      }
++      return ret;
++}
++
++static int
++pa2_cmd_write_do(
++      u8      cmd)
++{
++      int     ret;
++
++      ret = pa2_wait_on_input_buffer();
++      pa2_cmd_write(cmd);
++      ret |= pa2_wait_on_input_buffer();              /* XXX */
++      return ret;
++}
++
++static int
++pa2_data_write_do(
++      u8      data)
++{
++      int     ret;
++
++      ret = pa2_wait_on_input_buffer();
++      pa2_cmd_write(data);
++      ret |= pa2_wait_on_input_buffer();              /* XXX */
++      return ret;
++}
++
++static int
++pa2_ctl_mode_write_do(
++      u8      data)
++{
++      int     ret;
++
++      ret = pa2_cmd_write_do(PA2_CTL_CMD_MODE_WRITE);
++      ret |= pa2_data_write_do(data);
++      return ret;
++}
++
++static long
++pa2_grab(
++      struct file     *filp,
++      void __user     *p)
++{
++      DECLARE_WAITQUEUE(wait, current);
++      pa2_state_t     *sp;
++      int             ret;
++      unsigned long   expire;
++      unsigned char   byte;
++
++      sp = filp->private_data;
++      ASSERT(sp->ps_index == PA2_INDEX_KBD ||
++             sp->ps_index == PA2_INDEX_AUX);
++      ret = 0;
++      if (copy_from_user(&byte, p, 1)) {
++              ret = -EFAULT;
++              goto out;
++      }
++      PRINT_DEBUG("%s %s\n", byte == 0 ? "Relasing" : "Grabbing",
++                  sp->ps_index == PA2_INDEX_KBD ? "KBD" : "AUX");
++
++      /*
++       * Do not allow a grab unless we're seen an initialisation.
++       * Need both sem and spinlock to modify pa2_initialized, so can
++       * test with just one of these held.
++       * If currently being initialised, then wait.
++       */
++
++      spin_lock_irq(&pa2_i8042_lock);
++      if (byte == 1) {
++              /*
++               * If someone is initializing, then delay the grab until the
++               * initialization is complete.
++               * If it is us that is initializing, then this is an error (qemu
++               * is single threaded, so if we wait for the init to end we'll
++               * deadlock).
++               */
++
++              while (pa2_init_taskp != NULL) {
++                      if (pa2_init_taskp == current) {
++                              pa2_error("Trying to grab while initing\n");
++                              ret = -EAGAIN;  /* XXX */
++                              break;
++                      }
++                      PRINT_DEBUG("Waiting for initialization to complete\n");
++                      spin_unlock_irq(&pa2_i8042_lock);
++                      expire = schedule_timeout_interruptible(HZ / 10);
++                      spin_lock_irq(&pa2_i8042_lock);
++                      if (expire == 0) {
++                              continue;
++                      }
++                      ret = -EINTR;   /* XXX */
++                      break;
++              }
++              if (ret == 0 && pa2_initialized == 0) {
++                      ret = -EINVAL;
++              }
++      }
++      spin_unlock_irq(&pa2_i8042_lock);
++      if (ret != 0) {
++              goto out;
++      }
++
++      /*
++       * Once initialized, cannot be intialized again.  As we have an
++       * open, cannot become uninitialized, so safe to continue without lock.
++       */
++
++      spin_lock_irq(&sp->ps_lock);
++      if (byte == 0) {
++              if (sp->ps_grabbed != filp) {
++                      ret = -EINVAL;
++              } else {
++                      sp->ps_grabbed = NULL;
++                      if (sp->ps_waiting_grab != 0) {
++                              sp->ps_waiting_grab = 0;
++                              wake_up(&sp->ps_wait_grab);
++                      }
++              }
++              spin_unlock_irq(&sp->ps_lock);
++              goto out;
++      }
++
++
++      /*
++       * Set that we want focus, then wait for it to be given.
++       */
++
++      add_wait_queue(&sp->ps_wait_grab, &wait);
++      while (sp->ps_grabbed != NULL) {
++              sp->ps_waiting_grab = 1;
++              wake_up(&sp->ps_wait_poll);
++              set_current_state(TASK_INTERRUPTIBLE);
++              spin_unlock_irq(&sp->ps_lock);
++              PRINT_DEBUG("Waiting for device to come available...\n");
++              expire = schedule_timeout(HZ / 10);
++              spin_lock_irq(&sp->ps_lock);
++              if (expire == 0) {
++                      continue;
++              }
++              ret = -EINTR;   /* XXX */
++              break;
++      }
++      remove_wait_queue(&sp->ps_wait_grab, &wait);
++      if (ret == 0) {
++              sp->ps_grabbed = filp;
++              PRINT_DEBUG("Grabbed by %p\n", current);
++      }
++      spin_unlock_irq(&sp->ps_lock);
++
++      /*
++       * After opening the device, user-space driver should query
++       * us to see if the device has been initialized.
++       * On the last close, the device is considered to have been
++       * uninitialized.
++       */
++
++out:
++      return ret;
++}
++
++
++/*
++ * Write to the data port.
++ * If requested, clear the data queued for the given device.  This is needed
++ * as the write could be KBD or AUX cmd, and these clear any data queued for
++ * the device.  As there could be a pending data, clear this before sending
++ * the cmd.  Not 100% this last part is needed....XXX
++ */
++
++static inline int
++pa2_data_write(
++      struct file     *filp,
++      pa2_data_t      *arg,
++      int             index)
++{
++      pa2_state_t     *sp;
++      pa2_ps_t        *pp;
++      unsigned char   status;
++      unsigned char   data;
++      unsigned long   flags;
++      int             ret;
++
++      ASSERT(index == PA2_INDEX_KBD || index == PA2_INDEX_AUX);
++      sp = &pa2_state[index];
++
++      /*
++       * Debug only XXX
++       */
++
++      ret = 0;
++      spin_lock_irqsave(&sp->ps_lock, flags);
++      if (sp->ps_grabbed != filp && pa2_init_taskp != current) {
++              spin_unlock_irqrestore(&sp->ps_lock, flags);
++              pa2_error("Trying to write without perm: %p %p %p %p\n",
++                      sp->ps_grabbed, filp, pa2_init_taskp, current);
++              ret = -EACCES;
++              goto out;
++      }
++      spin_unlock_irqrestore(&sp->ps_lock, flags);
++
++      if (arg->pd_clear != 0) {
++              spin_lock_irqsave(&pa2_i8042_lock, flags);
++              status = pa2_status_read();
++              if (unlikely(status & PA2_CTL_STAT_OBF)) {
++                      data = pa2_data_read();
++              }
++      }
++      outb(arg->pd_data, PA2_DATA_PORT);
++      if (arg->pd_clear != 0) {
++              pp = sp->ps_queue;
++              spin_lock(&sp->ps_lock);
++              pp->pp_start = pp->pp_end;
++              spin_unlock(&sp->ps_lock);
++              spin_unlock_irqrestore(&pa2_i8042_lock, flags);
++      }
++
++out:
++      return ret;
++}
++
++static irqreturn_t
++pa2_irq(
++      int             irq,
++      void            *dev_id,
++      struct pt_regs  *regs)
++{
++      pa2_state_t     *sp;
++      pa2_ps_t        *pp;
++      unsigned long   flags;
++      unsigned int    next;
++      unsigned char   status;
++      unsigned char   data;
++      int             wake;
++      int             ret;
++
++      ASSERT(irq == PA2_KBD_IRQ || irq == PA2_AUX_IRQ);
++      PRINT_DEBUG("irq: %d\n", irq);
++      ret = 0;
++      spin_lock_irqsave(&pa2_i8042_lock, flags);
++      status = pa2_status_read();
++      if (unlikely(~status & PA2_CTL_STAT_OBF)) {
++              spin_unlock_irqrestore(&pa2_i8042_lock, flags);
++              PRINT_DEBUG("Interrupt %d without any data\n", irq);
++                goto out;
++      }
++      data = pa2_data_read();
++      spin_unlock_irqrestore(&pa2_i8042_lock, flags);
++
++      /*
++       * Can't rely on the IRQ to determine where the data is from (could
++       * be a data record read by the app has raced with this interrupt
++       * and read the data it was raised for).
++       */
++
++      sp = &pa2_state[PA2_INDEX_KBD];
++      if (status & PA2_CTL_STAT_AUX_OBF) {
++              sp = &pa2_state[PA2_INDEX_AUX];
++      }
++      pp = sp->ps_queue;
++      spin_lock_irqsave(&sp->ps_lock, flags);
++      next = (pp->pp_start + 1) & (PA2_BUF_SZ - 1);
++      if (next == pp->pp_end) {
++              pp->pp_overflow++;
++              spin_unlock_irqrestore(&sp->ps_lock, flags);
++              goto out;
++      }
++      pp->pp_entry[pp->pp_start].pe_data = data;
++      pp->pp_entry[pp->pp_start].pe_status = status;
++      pp->pp_start = next;
++      wake = 0;
++      if (waitqueue_active(&sp->ps_wait_poll)) {
++              wake = 1;
++      }
++      spin_unlock_irqrestore(&sp->ps_lock, flags);
++
++      if (wake) {
++              wake_up(&sp->ps_wait_poll);
++      }
++      ret = 1;
++
++out:
++      return IRQ_RETVAL(ret);
++}
++
++
++static int
++pa2_release(
++      struct inode    *inode,
++      struct file     *filp)
++{
++      pa2_state_t     *sp;
++      unsigned int    i;
++
++      ASSERT(filp->private_data != NULL);
++
++      PRINT_DEBUG("release\n");
++      sp = filp->private_data;
++      if (sp->ps_index != PA2_INDEX_CTL) {
++              ASSERT(sp->ps_index == PA2_INDEX_KBD ||
++                     sp->ps_index == PA2_INDEX_AUX);
++              spin_lock_irq(&sp->ps_lock);
++      PRINT_DEBUG("Release: %p %p...\n", sp->ps_grabbed, filp);
++              if (sp->ps_grabbed == filp) {
++              PRINT_DEBUG("Releasing grab...\n");
++                      sp->ps_grabbed = NULL;
++                      if (sp->ps_waiting_grab) {
++                              sp->ps_waiting_grab = 0;
++                              wake_up(&sp->ps_wait_grab);
++                      }
++              }
++              spin_unlock_irq(&sp->ps_lock);
++      }
++
++      spin_lock_irq(&pa2_i8042_lock);
++      if (sp->ps_index == PA2_INDEX_CTL) {
++
++              /*
++               * pa2_init_taskp needs all spinlocks to be modified (and,
++               * hence, only one to be read.
++               */
++
++              PRINT_DEBUG("release CTL\n");
++              sp = &pa2_state[0];
++              for (i = 0; i < 2; i++, sp++) {
++                      spin_lock(&sp->ps_lock);
++              }
++              if (pa2_init_taskp == current) {
++                      /* assert sem held XXX */
++                      pa2_init_taskp = NULL;
++                      up(&pa2_init_sem);
++              }
++              sp = &pa2_state[0];
++              for (i = 0; i < 2; i++, sp++) {
++                      spin_unlock(&sp->ps_lock);
++              }
++      }
++      ASSERT(pa2_open_count != 0);
++      pa2_open_count--;
++      if (pa2_open_count == 0) {
++              pa2_initialized = 0;
++              memset(&pa2_init_buffer, 0, sizeof (pa2_init_buffer));
++              /* XXX */
++              (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
++      }
++      spin_unlock_irq(&pa2_i8042_lock);
++
++      if (pa2_cmd_filp == filp) {
++              ASSERT(sp->ps_index == PA2_INDEX_CTL);
++              pa2_cmd_filp = NULL;
++              up(&pa2_cmd_sem);
++      }
++      filp->private_data = NULL;
++      return 0;
++}
++
++static int
++pa2_open(
++      struct inode    *inode,
++      struct file     *filp)
++{
++      struct proc_dir_entry   *dp;
++      int                     ret;
++      int             i;
++
++      ret = 0;
++      dp = PROC_I(inode)->pde;
++      for (i = 0; i < 3; i++) {
++              if (dp != pa2_proc_dev[i]) {
++                      continue;
++              }
++              if (i == PA2_INDEX_CTL) {
++                      filp->private_data = &pa2_ctl;
++              } else {
++                      filp->private_data = &pa2_state[i];
++              }
++              break;
++      }
++      ASSERT(i != 3);
++      ASSERT(filp->private_data != NULL);
++      spin_lock_irq(&pa2_i8042_lock);
++      pa2_open_count++;
++      spin_unlock_irq(&pa2_i8042_lock);
++      return ret;
++}
++
++/*
++ * Read records into given user-space buffer.
++ */
++
++static long
++pa2_record_read(
++      struct file             *filp,
++      void __user             *p)
++{
++      pa2_state_t             *sp;
++      pa2_ps_t                *pp;
++      pa2_rec_hd_t            phd;
++      pa2_entry_t             prec;
++      pa2_entry_t __user      *routp;
++      long                    ret;
++
++      ret = 0;
++      sp = filp->private_data;
++      ASSERT(sp->ps_index == PA2_INDEX_KBD ||
++             sp->ps_index == PA2_INDEX_AUX);
++      if (copy_from_user(&phd, p, sizeof (phd))) {
++              ret = -EFAULT;
++              goto out;
++      }
++      phd.ph_rec_num = 0;
++      phd.ph_focus = 0;
++      phd.ph_overflow = 0;
++      phd.ph_forced = 0;
++      routp = phd.ph_records;
++
++      pp = sp->ps_queue;
++      spin_lock_irq(&sp->ps_lock);
++      if (sp->ps_grabbed != filp && pa2_init_taskp != current) {
++              spin_unlock_irq(&sp->ps_lock);
++              pa2_error("Trying to access ports without perm: %p %p %p %p\n",
++                      sp->ps_grabbed, filp, pa2_init_taskp, current);
++              ret = -EACCES;
++              goto out;
++      }
++
++      /*
++       * While not out of records, and user-space buffer not full, copy
++       * out record.
++       */
++
++      while (pp->pp_end != pp->pp_start &&
++             phd.ph_rec_num != phd.ph_rec_size) {
++              prec.pe_data = pp->pp_entry[pp->pp_end].pe_data;
++              prec.pe_status = pp->pp_entry[pp->pp_end].pe_status;
++              pp->pp_end = (pp->pp_end + 1) & (PA2_BUF_SZ - 1);
++              spin_unlock_irq(&sp->ps_lock);
++
++              if (copy_to_user(routp, &prec, sizeof(prec))) {
++                      ret = -EFAULT;
++                      break;
++              }
++              routp++;
++              phd.ph_rec_num++;
++              spin_lock_irq(&sp->ps_lock);
++      }
++      if (sp->ps_waiting_grab) {
++              phd.ph_focus = 1;
++      }
++      if (pp->pp_overflow) {
++              pp->pp_overflow = 0;
++              phd.ph_overflow = 1;
++      }
++
++      /*
++       * If no records found, and space in buffer for at least one record,
++       * then add a force record.
++       * If the status indicates there is data available, and it matches the
++       * device we are reading from, then read data.
++       */
++
++      if (phd.ph_rec_num == 0 && phd.ph_rec_size != 0 && ret == 0) {
++              /* fetch a forced record */
++              phd.ph_forced = 1;
++              prec.pe_data = 0;
++              prec.pe_status = pa2_status_read();
++              if (prec.pe_status & PA2_CTL_STAT_OBF) {
++                      if (prec.pe_status & PA2_CTL_STAT_AUX_OBF) {
++                              if (sp->ps_index == PA2_INDEX_KBD) {
++                                      prec.pe_status &= ~PA2_CTL_STAT_AUX_OBF;
++                                      prec.pe_status &= ~PA2_CTL_STAT_OBF;
++                              } else {
++                                      prec.pe_data = pa2_data_read();
++                                      phd.ph_forced = 0;
++                              }
++                      } else {
++                              if (sp->ps_index == PA2_INDEX_AUX) {
++                                      prec.pe_status &= ~PA2_CTL_STAT_OBF;
++                              } else {
++                                      prec.pe_data = pa2_data_read();
++                                      phd.ph_forced = 0;
++                              }
++                      }
++              }
++              spin_unlock_irq(&sp->ps_lock);
++              if (copy_to_user(routp, &prec, sizeof(prec))) {
++                      ret = -EFAULT;
++              } else {
++                      phd.ph_rec_num++;
++              }
++      } else {
++              spin_unlock_irq(&sp->ps_lock);
++      }
++      if (copy_to_user(p, &phd, sizeof (phd))) {
++              ret = -EFAULT;
++      }
++
++out:
++      return ret;
++}
++
++static long
++pa2_ioctl_aux(
++      struct file     *filp,
++      unsigned int    cmd,
++      void __user     *p)
++{
++      long            ret;
++      pa2_data_t      arg;
++
++      ret = 0;
++      switch (cmd) {
++      case PA2_IOCTL_WR_DATA:
++              /* open for writing XXX */
++
++              /* grabbed or initializing XXX */
++              if (copy_from_user(&arg, p, sizeof (arg))) {
++                      return -EFAULT;
++              }
++              ret = pa2_data_write(filp, &arg, PA2_INDEX_AUX);
++              break;
++      
++      case PA2_IOCTL_RD_RECORD:
++              ret = pa2_record_read(filp, p);
++              break;
++
++      case PA2_IOCTL_GRAB:
++              ret = pa2_grab(filp, p);
++              break;
++
++      default:
++              ret = -EINVAL;
++              break;
++      }
++      return ret;
++}
++
++static long
++pa2_ioctl_kbd(
++      struct file     *filp,
++      unsigned int    cmd,
++      void __user     *p)
++{
++      long            ret;
++      pa2_data_t      arg;
++
++      ret = 0;
++      switch (cmd) {
++      case PA2_IOCTL_WR_DATA:
++              /* write directly from the port */
++              /* mask interrupts/spinlock? XXX */
++              if (copy_from_user(&arg, p, sizeof (arg))) {
++                      return -EFAULT;
++              }
++              ret = pa2_data_write(filp, &arg, PA2_INDEX_KBD);
++              break;
++      
++      case PA2_IOCTL_RD_RECORD:
++              ret = pa2_record_read(filp, p);
++              break;
++
++      case PA2_IOCTL_GRAB:
++              ret = pa2_grab(filp, p);
++              break;
++
++      default:
++              ret = -EINVAL;
++              break;
++      }
++      return ret;
++}
++
++static long
++pa2_ioctl_ctl(
++      struct file     *filp,
++      unsigned int    cmd,
++      void __user     *p)
++{
++      pa2_state_t     *sp;
++      long            ret;
++      u8              byte;
++
++      sp = filp->private_data;
++      ASSERT(filp->private_data == &pa2_ctl);
++      ret = 0;
++      switch (cmd) {
++      case PA2_IOCTL_WR_CMD:
++              /* write access? XXX */
++              if (copy_from_user(&byte, p, 1)) {
++                      return -EFAULT;
++              }
++              pa2_cmd_write(byte);
++              break;
++
++      case PA2_IOCTL_GRAB:
++              ret = pa2_grab(filp, p);
++              break;
++
++      case PA2_IOCTL_INIT_LOCK:
++              /*
++               * As the device cannot be grabbed until it has been
++               * initialized, XXX
++               */
++              if (copy_from_user(&byte, p, 1)) {
++                      return -EFAULT;
++              }
++              if (byte == 0) {
++                      if (pa2_init_taskp != current) {
++                              pa2_error("bad unlock\n");
++                              return -EINVAL;
++                      }
++                      pa2_init_taskp = NULL;
++                      up(&pa2_init_sem);
++                      return 0;
++              }
++              down(&pa2_init_sem);
++              /* take all spinlocks XXX */
++              pa2_init_taskp = current;
++              break;
++
++      case PA2_IOCTL_INIT_QUERY:
++              if (pa2_init_taskp != current) {
++                      pa2_error("bad query\n");
++                      ret = -EINVAL;
++                      break;
++              }
++              if (copy_to_user(p, &pa2_init_buffer,
++                               sizeof (pa2_init_buffer))) {
++                      ret = -EFAULT;
++                      break;
++              }
++              break;
++
++      case PA2_IOCTL_INIT_SET:
++              if (pa2_init_taskp != current) {
++                      pa2_error("bad set\n");
++                      ret = -EPERM;
++                      break;
++              }
++              /* check if already initialized XXX */
++              if (copy_from_user(&pa2_init_buffer_tmp, p,
++                                 sizeof (pa2_init_buffer_tmp))) {
++                      ret = -EFAULT;
++                      break;
++              }
++              if (pa2_init_buffer_tmp.di_len > PA2_DATA_INIT_MAX) {
++                      ret = -EINVAL;
++                      break;
++              }
++              spin_lock_irq(&pa2_i8042_lock);
++              memcpy(&pa2_init_buffer, &pa2_init_buffer_tmp,
++                     sizeof (pa2_init_buffer_tmp));
++              pa2_initialized = 1;
++              spin_unlock_irq(&pa2_i8042_lock);
++              break;
++
++      case PA2_IOCTL_INIT_CMD:
++              if (copy_from_user(&byte, p, 1)) {
++                      ret = -EFAULT;
++                      break;
++              }
++              if (byte == 1) {
++                      PRINT_DEBUG("Taking init cmd: %p\n", current);
++                      if (down_interruptible(&pa2_cmd_sem)) {
++                              ret = -ERESTARTSYS;
++                              break;
++                      }
++                      pa2_cmd_filp = filp;
++                      break;
++              }
++              PRINT_DEBUG("Releasing init_cmd: %p\n", current);
++              pa2_cmd_filp = NULL;
++              up(&pa2_cmd_sem);
++              break;
++
++      default:
++              ret = -EINVAL;
++              break;
++      }
++      return ret;
++}
++
++static long
++pa2_ioctl(
++      struct file     *filp,
++      unsigned int    cmd,
++      unsigned long   arg)
++{
++      pa2_state_t     *sp;
++      long            ret;
++
++      sp = filp->private_data;
++      switch (sp->ps_index) {
++      case PA2_INDEX_KBD:
++              ret = pa2_ioctl_kbd(filp, cmd, (void __user *)arg);
++              break;
++
++      case PA2_INDEX_AUX:
++              ret = pa2_ioctl_aux(filp, cmd, (void __user *)arg);
++              break;
++
++      case PA2_INDEX_CTL:
++              ret = pa2_ioctl_ctl(filp, cmd, (void __user *)arg);
++              break;
++
++      default:
++              ret = -EINVAL;
++              break;
++      }
++      return ret;
++}
++
++static unsigned int
++pa2_poll(
++      struct file     *filp,
++      poll_table      *wait)
++{
++      pa2_state_t     *sp;
++      pa2_ps_t        *pp;
++      unsigned int    mask;
++
++      /*
++       * Should never poll the ctl.
++       */
++
++      sp = filp->private_data;
++      if (sp->ps_index != PA2_INDEX_KBD && sp->ps_index != PA2_INDEX_AUX) {
++              ASSERT(0);
++              return POLLOUT | POLLWRNORM | POLLIN | POLLRDNORM;
++      }
++      poll_wait(filp, &sp->ps_wait_poll, wait);
++      pp = sp->ps_queue;
++      mask = POLLOUT | POLLWRNORM;
++      spin_lock_irq(&sp->ps_lock);
++      if (pp->pp_start != pp->pp_end || sp->ps_waiting_grab) {
++              mask |= POLLIN | POLLRDNORM;
++      }
++      spin_unlock_irq(&sp->ps_lock);
++      return mask;
++}
++
++static struct file_operations pa2_file_ops = {
++      .owner          = THIS_MODULE,
++      .open           = pa2_open,
++      .unlocked_ioctl = pa2_ioctl,
++      .poll           = pa2_poll,
++      .release        = pa2_release,
++};
++
++static void __exit
++pa2_deinit(
++      void)
++{
++      unsigned int    i;
++
++      /*
++       * Should place 8042 into a safe state; KBD and AUX devices disabled,
++       * and interrupts disabled too.
++       */
++
++      (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
++      for (i = 0; i < 3; i++) {
++              if (pa2_proc_dev[i] != NULL) {
++                      remove_proc_entry(pa2_path[i], NULL);
++                      pa2_proc_dev[i] = NULL;
++              }
++      }
++      free_irq(PA2_KBD_IRQ, NULL);
++      free_irq(PA2_AUX_IRQ, NULL);
++      return;
++}
++
++static int __init
++pa2_init(
++      void)
++{
++      pa2_state_t     *sp;
++      unsigned int    i;
++      int             ret;
++
++      /*
++       * Grab keyboard and aux interrupts.
++       */
++
++      init_MUTEX(&pa2_init_sem);
++      for (i = 0; i < 3; i++) {
++              pa2_proc_dev[i] = create_proc_entry(pa2_path[i], 0644, NULL);
++              if (pa2_proc_dev[i] != NULL) {
++                      pa2_proc_dev[i]->proc_fops = &pa2_file_ops;
++              }
++      }
++
++      sp = &pa2_state[0];
++      for (i = 0; i < 2; i++, sp++) {
++              spin_lock_init(&sp->ps_lock);
++              sp->ps_queue = &pa2_queue[i];
++              sp->ps_index = i;
++              init_waitqueue_head(&sp->ps_wait_grab);
++              init_waitqueue_head(&sp->ps_wait_poll);
++      }
++      spin_lock_init(&pa2_ctl.ps_lock);
++      pa2_ctl.ps_index = PA2_INDEX_CTL;
++
++      PRINT_DEBUG("Requesting interrupts...\n");
++      ret = request_irq(PA2_KBD_IRQ, pa2_irq, 0, "kbd", NULL);
++      if (ret != 0) {
++              pa2_error("unable to get keyboard interrupt\n");
++              /* remove procs XXX */
++              goto out;
++      }
++      ret = request_irq(PA2_AUX_IRQ, pa2_irq, 0, "aux", NULL);
++      if (ret != 0) {
++              pa2_error("unable to get aux interrupt\n");
++              /* remove procs XXX */
++              free_irq(PA2_KBD_IRQ, NULL);    /* ugh, NULL!!! */
++              goto out;
++      }
++
++      (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
++
++out:
++      return ret;
++}
++
++module_init(pa2_init);
++module_exit(pa2_deinit);
++
++MODULE_LICENSE("Dual BSD/GPL");